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


137

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

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유형은 매우 비효율적 정말?


7
이것은 보류 이유의 지속적인 전투에서 가장 아이러니 한 충돌 중 하나입니다. John과 Hans의 두 가지 훌륭한 답변은 이 질문에 대한 답변이 사실, 참고 문헌이 아닌 의견에 거의 전적으로 근거한 경향이 있지만, 또는 특정 전문 지식.
— TaW

12
@TaW : 내 생각에 가까운 투표는 답변이 아니라 OP가 처음 질문을 할 때의 원래의 톤으로 인한 것입니다. 그들은 분명히 싸움을 시작하고 완전히 삭제 된 의견에 이것을 보여 주었다. 부스러기의 대부분은 깔개 밑에서 휩쓸었지만 개정 내역을 확인하여 의미하는 바를 살펴보십시오.
— BoltClock

1
왜 BitArray를 사용하지 않습니까?
— ded '

답변:


242

부울 유형은 언어 런타임 사이에는 많은 호환되지 않는 선택과 체크 무늬 역사를 가지고 있습니다. 이것은 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 명령을 내 보내지 않는 핵심 이유는 명시 적으로 래핑되지 않으면 정렬 보증을받을 수 없습니다.



2
관심이 있지만 정보가없는 독자라면 마지막 단락이 실제로 32 바이트 가 아닌 비트를 읽어야하는지 명확하게 설명 하시겠습니까?
— Silly Freak

3
왜 내가이 모든 것을 읽었는지 잘 모르겠습니다 (이 많은 세부 사항이 필요하지 않기 때문에). 매우 매력적이며 잘 작성되었습니다.
— Frank V

2
@Silly- 바이트 입니다. AVX는 512 비트 변수를 사용하여 단일 명령어로 8 개의 부동 소수점 값에서 수학을 수행합니다. 이러한 512 비트 변수는 32에 대한 정렬이 필요합니다.
— Hans Passant

3
와! 한 게시물은 이해하기 어려운 많은 주제를 주었다. 그렇기 때문에 나는 단지 최고의 질문을 읽는 것을 좋아합니다.
— Chaitanya Gadkari

151

첫째, 이것은 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 바이트를 차지하는 것으로 표시됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.