[C#] C #에서 부울의 크기는 얼마입니까? 실제로 4 바이트가 필요합니까?

바이트 배열과 부울 배열을 가진 두 개의 구조체가 있습니다.

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에서 수행되었으며 BOOLC 컴파일러의 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 바이트를 차지하는 것으로 표시됩니다.


답변