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.