바이트 배열과 부울 배열을 가진 두 개의 구조체가 있습니다.
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] values;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public bool[] values;
}
그리고 다음 코드는
class main
{
public static void Main()
{
Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
Console.ReadKey();
}
}
그 결과 다음과 같은 결과가 나옵니다.
sizeof array of bytes: 3
sizeof array of bools: 12
a boolean
는 4 바이트의 저장 공간이 필요한 것 같습니다 . 이상적으로는 boolean
1 비트 ( false
또는 true
, 0
또는 1
등) 만 사용합니다.
여기서 무슨 일이 일어나고 있습니까? 는 IS boolean
유형은 매우 비효율적 정말?
답변
부울 유형은 언어 런타임 사이에는 많은 호환되지 않는 선택과 체크 무늬 역사를 가지고 있습니다. 이것은 C 언어를 발명 한 데니스 리치 (Dennis Ritchie)가 만든 역사적인 디자인 선택으로 시작되었습니다. 부울 유형 이 없었 습니다. 대안은 0 값이 false를 나타내고 다른 값이 true 로 간주 된 int 입니다 .
이 선택은 pinvoke를 사용하는 주된 이유 인 Winapi에서 수행되었으며 BOOL
C 컴파일러의 int 키워드에 대한 별칭 인 typedef 가 있습니다. 명시적인 [MarshalAs] 속성을 적용하지 않으면 C # bool 이 BOOL로 변환되어 4 바이트 길이의 필드가 생성됩니다.
무엇을 하든지 struct 선언은 당신이 사용하는 언어로 만든 런타임 선택과 일치해야합니다. 언급했듯이 winapi의 경우 BOOL이지만 대부분의 C ++ 구현에서는 바이트를 선택했지만 대부분의 COM 자동화 interop은 짧은 VARIANT_BOOL을 사용합니다 .
C # 의 실제 크기 bool
는 1 바이트입니다. CLR의 강력한 디자인 목표는 찾을 수 없다는 것입니다. 레이아웃은 프로세서에 너무 의존하는 구현 세부 사항입니다. 프로세서는 변수 유형 및 정렬에 매우 까다롭기 때문에 잘못된 선택은 성능에 크게 영향을 미치고 런타임 오류를 일으킬 수 있습니다. .NET은 레이아웃을 발견 할 수 없도록하여 실제 런타임 구현에 의존하지 않는 범용 유형 시스템을 제공 할 수 있습니다.
다시 말해, 레이아웃을 구성하려면 런타임에 항상 구조를 마샬링해야합니다. 이때 내부 레이아웃에서 interop 레이아웃으로 변환됩니다 . 레이아웃이 동일하면 매우 빠르며 필드를 다시 정렬해야 할 때 속도가 느릴 수 있습니다. 왜냐하면 항상 구조체의 복사본을 만들어야하기 때문입니다. 이 기술 용어는 blittable PInvoke를 마샬은 단순히 포인터를 전달 할 수 있기 때문에 네이티브 코드에 blittable 구조체를 통과하는 것은 빠르다.
부울 이 단일 비트가 아닌 핵심 이유도 성능입니다 . 직접 주소를 지정할 수있는 프로세서는 거의 없으며 가장 작은 단위는 바이트입니다. 추가 명령이 무료로 제공되지 않는 바이트의 비트를 물고기가 필요합니다. 그리고 그것은 결코 원자 적이 지 않습니다.
C # 컴파일러는 그렇지 않으면 1 바이트가 필요하다는 것을 알려주지 않습니다 sizeof(bool)
. 이것은 필드가 런타임에 몇 바이트를 차지하는 지에 대한 환상적인 예측기는 아니지만 CLR은 .NET 메모리 모델을 구현해야하며 간단한 변수 업데이트가 원자 적이라고 약속합니다 . 따라서 메모리에서 변수를 올바르게 정렬해야 프로세서가 단일 메모리 버스 주기로 변수를 업데이트 할 수 있습니다. 종종 bool은 실제로이 때문에 메모리에 4 또는 8 바이트가 필요합니다. 다음 멤버가 올바르게 정렬 되도록 추가 패딩이 추가되었습니다 .
CLR은 실제로 레이아웃을 발견 할 수 없다는 장점을 가지고 있으며, 클래스의 레이아웃을 최적화하고 필드를 다시 정렬하여 패딩을 최소화 할 수 있습니다. 따라서 bool + int + bool 멤버가있는 클래스가 있으면 1 + (3) + 4 + 1 + (3) 바이트의 메모리가 필요하며 (3)은 패딩입니다. 바이트. 50 % 낭비. 자동 레이아웃은 1 + 1 + (2) + 4 = 8 바이트로 재 배열됩니다. 클래스에만 자동 레이아웃이 있고 구조체에는 기본적으로 순차적 레이아웃이 있습니다.
더 부끄럽게도 bool 은 AVX 명령어 세트를 지원하는 최신 C ++ 컴파일러로 컴파일 된 C ++ 프로그램에서 최대 32 바이트를 요구할 수 있습니다. 32 바이트 정렬 요구 사항을 부과하는 bool 변수는 31 바이트의 패딩으로 끝날 수 있습니다. 또한 .NET 지터가 SIMD 명령을 내 보내지 않는 핵심 이유는 명시 적으로 래핑되지 않으면 정렬 보증을받을 수 없습니다.
답변
첫째, 이것은 interop의 크기 일뿐 입니다. 배열의 관리 코드에서 크기를 나타내지 않습니다. bool
적어도 내 컴퓨터에서는 1 바이트 입니다. 이 코드로 직접 테스트 할 수 있습니다.
using System;
class Program
{
static void Main(string[] args)
{
int size = 10000000;
object array = null;
long before = GC.GetTotalMemory(true);
array = new bool[size];
long after = GC.GetTotalMemory(true);
double diff = after - before;
Console.WriteLine("Per value: " + diff / size);
// Stop the GC from messing up our measurements
GC.KeepAlive(array);
}
}
이제 값을 기준으로 배열을 마샬링하려면 설명서에 다음과 같이 나와 있습니다.
MarshalAsAttribute.Value 속성이로 설정된
ByValArray
경우 배열의 요소 수를 나타내도록 SizeConst 필드를 설정해야합니다.ArraySubType
필드를 선택적으로 포함 할 수UnmanagedType
는 스트링 타입과 구별 할 필요가있을 때 배열 요소를. 이UnmanagedType
요소는 구조에서 필드로 요소가 나타나는 배열에서만 사용할 수 있습니다 .
그래서 우리는을 봅니다 ArraySubType
.
이 매개 변수를
UnmanagedType
열거 형 의 값으로 설정 하여 배열 요소의 유형을 지정할 수 있습니다 . 유형을 지정하지 않으면 관리되는 배열의 요소 유형에 해당하는 기본 관리되지 않는 유형이 사용됩니다.
이제을 보면 다음과 UnmanagedType
같습니다.
Bool
4 바이트 부울 값 (true! = 0, false = 0)입니다. 이것이 Win32 BOOL 타입입니다.
따라서 이것이 기본값이며 bool
, Win32 BOOL 유형에 해당하기 때문에 4 바이트입니다. 따라서 BOOL
배열을 기대하는 코드와 상호 작용하는 경우 원하는 것을 정확하게 수행합니다.
이제 대신 다음 ArraySubType
과 같이 I1
문서화 할 수 있습니다 .
1 바이트 부호있는 정수 이 멤버를 사용하여 부울 값을 1 바이트 C 스타일 부울 (true = 1, false = 0)로 변환 할 수 있습니다.
따라서 상호 운용중인 코드가 값 당 1 바이트를 예상하면 다음을 사용하십시오.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;
그러면 코드가 예상대로 값당 1 바이트를 차지하는 것으로 표시됩니다.