Kích thước của một boolean trong C # là gì? Nó thực sự mất 4 byte?


137

Tôi có hai cấu trúc với các mảng byte và booleans:

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;
}

Và mã sau đây:

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();
    }
}

Điều đó mang lại cho tôi đầu ra sau:

sizeof array of bytes: 3
sizeof array of bools: 12

Dường như booleanmất 4 byte dung lượng lưu trữ. Lý tưởng nhất là boolean chỉ mất một bit ( falsehoặc true, 0hoặc 1, v.v.).

Chuyện gì đang xảy ra ở đây? Là booleanloại thực sự rất không hiệu quả?


7
Đây là một trong những cuộc đụng độ mỉa mai nhất trong cuộc chiến vì lý do đang diễn ra: Hai câu trả lời xuất sắc của John và Hans chỉ đưa ra, mặc dù câu trả lời cho câu hỏi này sẽ có xu hướng gần như hoàn toàn dựa trên ý kiến, thay vì sự kiện, tài liệu tham khảo, hoặc chuyên môn cụ thể.
TaW 14/2/2015

12
@TaW: Tôi đoán là số phiếu gần không phải do câu trả lời mà là giọng điệu ban đầu của OP khi họ đưa ra câu hỏi - họ rõ ràng có ý định bắt đầu một cuộc chiến và hoàn toàn thể hiện điều này trong các bình luận hiện đã bị xóa. Hầu hết các tàu đã bị cuốn theo tấm thảm, nhưng hãy xem lịch sử sửa đổi để có cái nhìn thoáng qua về ý tôi.
BoltClock

1
Tại sao không sử dụng BitArray?
khấu trừ '

Câu trả lời:


242

Các bool loại có một lịch sử ca rô với nhiều sự lựa chọn không tương thích giữa các runtimes ngôn ngữ. Điều này bắt đầu với một lựa chọn thiết kế lịch sử được thực hiện bởi Dennis Ritchie, người đã phát minh ra ngôn ngữ C. Nó không có kiểu bool , thay thế là int trong đó giá trị 0 biểu thị sai và bất kỳ giá trị nào khác được coi là đúng .

Sự lựa chọn này đã được chuyển tiếp trong Winapi, lý do chính để sử dụng pinvoke, nó có một typedef BOOLlà bí danh cho từ khóa int của trình biên dịch C. Nếu bạn không áp dụng thuộc tính [MarshalAs] rõ ràng thì bool C # được chuyển đổi thành BOOL, do đó tạo ra một trường dài 4 byte.

Dù bạn làm gì, khai báo cấu trúc của bạn cần phải phù hợp với lựa chọn thời gian chạy được thực hiện bằng ngôn ngữ bạn giao tiếp. Như đã lưu ý, BOOL cho winapi nhưng hầu hết các triển khai C ++ đều chọn byte , hầu hết interop Tự động hóa COM sử dụng VariANT_BOOL là một đoạn ngắn .

Các thực tế kích thước của một C # boollà một byte. Mục tiêu thiết kế mạnh mẽ của CLR là bạn không thể tìm ra. Bố cục là một chi tiết thực hiện phụ thuộc vào bộ xử lý quá nhiều. Bộ xử lý rất kén chọn các loại biến và căn chỉnh, các lựa chọn sai có thể ảnh hưởng đáng kể đến hiệu suất và gây ra lỗi thời gian chạy. Bằng cách làm cho bố cục không thể phát hiện được, .NET có thể cung cấp một hệ thống loại phổ quát không phụ thuộc vào việc thực hiện thời gian chạy thực tế.

Nói cách khác, bạn luôn phải sắp xếp một cấu trúc trong thời gian chạy để sắp xếp bố cục. Tại thời điểm chuyển đổi từ bố trí nội bộ sang bố trí interop được thực hiện. Điều đó có thể rất nhanh nếu bố cục giống hệt nhau, chậm khi các trường cần được sắp xếp lại vì điều đó luôn đòi hỏi phải tạo một bản sao của cấu trúc. Thuật ngữ kỹ thuật cho điều này là có thể hiểu được , việc chuyển một cấu trúc có thể xóa được sang mã gốc rất nhanh bởi vì trình sắp xếp pinvoke có thể chỉ đơn giản là vượt qua một con trỏ.

Hiệu suất cũng là lý do cốt lõi tại sao một bool không phải là một bit. Có một vài bộ xử lý làm cho một địa chỉ trực tiếp một chút, đơn vị nhỏ nhất là một byte. Một hướng dẫn bổ sung được yêu cầu để loại bỏ bit ra khỏi byte, điều đó không miễn phí. Và nó không bao giờ là nguyên tử.

Trình biên dịch C # không ngại nói với bạn rằng phải mất 1 byte, hãy sử dụng sizeof(bool). Đây vẫn chưa phải là một công cụ dự đoán tuyệt vời cho bao nhiêu byte mà một trường mất trong thời gian chạy, CLR cũng cần thực hiện mô hình bộ nhớ .NET và nó hứa hẹn rằng các cập nhật biến đơn giản là nguyên tử . Điều đó đòi hỏi các biến phải được căn chỉnh chính xác trong bộ nhớ để bộ xử lý có thể cập nhật nó với một chu kỳ bus bộ nhớ duy nhất. Khá thường xuyên, một bool thực sự cần 4 hoặc 8 byte trong bộ nhớ vì điều này. Phần đệm thêm đã được thêm vào để đảm bảo rằng thành viên tiếp theo được căn chỉnh chính xác.

CLR thực sự tận dụng bố cục là không thể phát hiện được, nó có thể tối ưu hóa bố cục của một lớp và sắp xếp lại các trường để phần đệm được giảm thiểu. Vì vậy, giả sử, nếu bạn có một lớp có thành viên bool + int + bool thì sẽ mất 1 + (3) + 4 + 1 + (3) byte bộ nhớ, (3) là phần đệm, tổng cộng là 12 byte. 50% chất thải. Bố cục tự động sắp xếp lại thành 1 + 1 + (2) + 4 = 8 byte. Chỉ có một lớp có bố cục tự động, các cấu trúc có bố cục tuần tự theo mặc định.

Đáng sợ hơn, một bool có thể cần tới 32 byte trong chương trình C ++ được biên dịch với trình biên dịch C ++ hiện đại hỗ trợ tập lệnh AVX. Trong đó áp đặt yêu cầu căn chỉnh 32 byte, biến bool có thể kết thúc với 31 byte đệm. Ngoài ra, lý do cốt lõi khiến jitter .NET không phát ra các hướng dẫn SIMD, trừ khi được gói rõ ràng, nó không thể có được sự đảm bảo căn chỉnh.



2
Đối với một người đọc quan tâm nhưng không hiểu biết, bạn có làm rõ liệu đoạn cuối có thực sự nên đọc 32 byte và không phải là bit không?
Silly Freak

3
Không chắc tại sao tôi chỉ đọc tất cả những điều này (vì tôi không cần nhiều chi tiết này) nhưng điều đó thật hấp dẫn và được viết tốt.
Frank V

2
@Silly - đó là byte . AVX sử dụng các biến 512 bit để làm toán trên 8 giá trị dấu phẩy động với một lệnh đơn. Biến 512 bit như vậy yêu cầu căn chỉnh đến 32.
Hans Passant

3
Ồ một bài viết đã cho rất nhiều chủ đề để hiểu. Đó là lý do tại sao tôi thích đọc những câu hỏi hàng đầu.
Chaitanya Gadkari

151

Thứ nhất, đây chỉ là kích thước cho interop. Nó không đại diện cho kích thước trong mã được quản lý của mảng. Đó là 1 byte mỗi bool- ít nhất là trên máy của tôi. Bạn có thể tự kiểm tra nó với mã này:

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); 
    } 
}

Bây giờ, đối với các mảng sắp xếp theo giá trị, như bạn là, tài liệu nói:

Khi thuộc tính MarshalAsAttribution.Value được đặt thành ByValArray, trường SizeConst phải được đặt để chỉ ra số lượng phần tử trong mảng. Các ArraySubTypelĩnh vực có thể tùy chọn chứa UnmanagedTypecác phần tử mảng khi nó là cần thiết để phân biệt giữa các loại chuỗi. Bạn chỉ có thể sử dụng điều này UnmanagedTypetrên một mảng có các phần tử xuất hiện dưới dạng các trường trong cấu trúc.

Vì vậy, chúng tôi xem xét ArraySubType, và có tài liệu về:

Bạn có thể đặt tham số này thành một giá trị từ UnmanagedTypebảng liệt kê để chỉ định loại phần tử của mảng. Nếu một loại không được chỉ định, loại không được quản lý mặc định tương ứng với loại phần tử của mảng được quản lý sẽ được sử dụng.

Bây giờ nhìn vào UnmanagedType, có:

Bool
Giá trị Boolean 4 byte (true! = 0, false = 0). Đây là loại Win32 BOOL.

Vì vậy, đó là mặc định cho boolvà đó là 4 byte vì tương ứng với loại BO32 Win32 - vì vậy nếu bạn đang tương tác với mã mong đợi một BOOLmảng, nó sẽ thực hiện chính xác những gì bạn muốn.

Bây giờ bạn có thể chỉ định ArraySubTypenhư I1thay vào đó, được ghi lại như sau:

Một số nguyên có ký hiệu 1 byte. Bạn có thể sử dụng thành viên này để chuyển đổi giá trị Boolean thành bool kiểu C 1 byte (true = 1, false = 0).

Vì vậy, nếu mã bạn đang tương tác với mong đợi 1 byte cho mỗi giá trị, chỉ cần sử dụng:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;

Mã của bạn sau đó sẽ hiển thị rằng chiếm tới 1 byte cho mỗi giá trị, như mong đợi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.