Berapa ukuran boolean dalam C #? Apakah ini benar-benar membutuhkan 4-byte?

138

Saya memiliki dua struct dengan array byte dan boolean:

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

Dan kode berikut:

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

Itu memberi saya output berikut:

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

Tampaknya booleandibutuhkan 4 byte penyimpanan. Idealnya sebuah boolean hanya membutuhkan satu bit ( falseatau true, 0atau 1, dll ..).

Apa yang terjadi disini? Apakah booleanjenisnya benar-benar tidak efisien?

biv
sumber
7
Ini adalah salah satu bentrokan paling ironis dalam pertarungan alasan menahan yang sedang berlangsung: Dua jawaban luar biasa dari John dan Hans baru saja dibuat, meskipun jawaban atas pertanyaan ini cenderung hampir seluruhnya didasarkan pada opini, bukan fakta, referensi, atau keahlian khusus.
TaW
12
@TaW: Dugaan saya adalah bahwa suara yang ditutup bukan karena jawaban tetapi nada asli OP ketika mereka pertama kali mengajukan pertanyaan - mereka jelas bermaksud untuk memulai perkelahian dan langsung menunjukkan ini di komentar yang sekarang sudah dihapus. Sebagian besar sampah telah disapu ke bawah permadani, tetapi periksa riwayat revisi untuk melihat sekilas apa yang saya maksud.
BoltClock
1
Mengapa tidak menggunakan BitArray?
ded '16

Jawaban:

245

The bool jenis memiliki sejarah kotak-kotak dengan banyak pilihan sesuai antara runtimes bahasa. Ini dimulai dengan pilihan desain historis yang dibuat oleh Dennis Ritchie, orang yang menemukan bahasa C. Itu tidak memiliki tipe bool , alternatifnya adalah int di mana nilai 0 mewakili salah dan nilai lainnya dianggap benar .

Pilihan ini dibawa ke depan di Winapi, alasan utama untuk menggunakan pinvoke, ia memiliki typedef BOOLyang merupakan alias untuk kata kunci int kompiler C. Jika Anda tidak menerapkan atribut [MarshalAs] eksplisit maka C # bool akan diubah menjadi BOOL, sehingga menghasilkan bidang yang panjangnya 4 byte.

Apa pun yang Anda lakukan, deklarasi struct Anda harus sesuai dengan pilihan waktu proses yang dibuat dalam bahasa yang Anda gunakan. Seperti disebutkan, BOOL untuk winapi tetapi sebagian besar implementasi C ++ memilih byte , sebagian besar interop COM Automation menggunakan VARIANT_BOOL yang merupakan singkatan .

The aktual ukuran C # booladalah salah satu byte. Tujuan desain CLR yang kuat adalah bahwa Anda tidak dapat menemukannya. Tata letak adalah detail implementasi yang terlalu bergantung pada prosesor. Prosesor sangat pilih-pilih tentang jenis dan penyelarasan variabel, pilihan yang salah dapat memengaruhi kinerja secara signifikan dan menyebabkan kesalahan waktu proses. Dengan membuat tata letak tidak dapat ditemukan, .NET dapat menyediakan sistem tipe universal yang tidak bergantung pada implementasi runtime yang sebenarnya.

Dengan kata lain, Anda harus selalu menyusun struktur pada waktu proses untuk memastikan tata letaknya. Pada saat konversi dari tata letak internal ke tata letak interop dilakukan. Itu bisa sangat cepat jika tata letaknya identik, lambat ketika bidang perlu diatur ulang karena itu selalu membutuhkan pembuatan salinan struct. Istilah teknis untuk ini adalah blittable , meneruskan struct blittable ke kode native dengan cepat karena marshaller pinvoke dapat dengan mudah meneruskan sebuah pointer.

Kinerja juga merupakan alasan utama mengapa bool tidak sedikit pun. Ada beberapa prosesor yang membuat sedikit dapat langsung dialamatkan, unit terkecil adalah byte. Sebuah tambahan instruksi diperlukan untuk ikan sedikit keluar dari byte, yang tidak datang secara gratis. Dan itu tidak pernah atom.

Kompiler C # tidak malu untuk memberitahu Anda bahwa ini membutuhkan 1 byte, gunakan sizeof(bool). Ini masih bukan prediktor yang bagus untuk berapa banyak byte yang dibutuhkan sebuah field saat runtime, CLR juga perlu mengimplementasikan model memori .NET dan menjanjikan bahwa pembaruan variabel sederhana bersifat atomic . Itu membutuhkan variabel untuk diselaraskan dengan benar dalam memori sehingga prosesor dapat memperbaruinya dengan satu siklus bus memori. Cukup sering, bool sebenarnya membutuhkan 4 atau 8 byte dalam memori karena ini. Bantalan ekstra yang ditambahkan untuk memastikan bahwa anggota berikutnya disejajarkan dengan benar.

CLR sebenarnya memanfaatkan tata letak yang tidak dapat ditemukan, ini dapat mengoptimalkan tata letak kelas dan mengatur ulang bidang sehingga padding diminimalkan. Jadi, katakanlah, jika Anda memiliki kelas dengan anggota bool + int + bool maka akan membutuhkan 1 + (3) + 4 + 1 + (3) byte memori, (3) adalah padding, dengan total 12 byte. 50% limbah. Tata letak otomatis diatur ulang menjadi 1 + 1 + (2) + 4 = 8 byte. Hanya kelas yang memiliki tata letak otomatis, struct memiliki tata letak berurutan secara default.

Lebih suram lagi, bool dapat memerlukan sebanyak 32 byte dalam program C ++ yang dikompilasi dengan kompiler C ++ modern yang mendukung set instruksi AVX. Yang memberlakukan persyaratan penyelarasan 32-byte, variabel bool mungkin berakhir dengan padding 31 byte. Juga alasan inti mengapa jitter .NET tidak memancarkan instruksi SIMD, kecuali dibungkus secara eksplisit, itu tidak bisa mendapatkan jaminan penyelarasan.

Hans Passant
sumber
2
Untuk pembaca yang tertarik tetapi kurang informasi, apakah Anda akan menjelaskan apakah paragraf terakhir benar-benar harus membaca 32 byte dan bukan bit ?
Silly Freak
3
Tidak yakin mengapa saya hanya membaca semua ini (karena saya tidak membutuhkan banyak detail) tetapi itu menarik dan ditulis dengan baik.
Frank V
3
@Silly - itu adalah byte . AVX menggunakan variabel 512 bit untuk melakukan matematika pada 8 nilai floating point dengan satu instruksi. Variabel 512 bit seperti itu membutuhkan penyelarasan ke 32.
Hans Passant
3
Wow! satu posting memberikan banyak topik untuk dipahami. Itulah mengapa saya suka membaca pertanyaan teratas.
Chaitanya Gadkari
154

Pertama, ini hanya ukuran untuk interop. Ini tidak mewakili ukuran dalam kode array yang dikelola. Itu 1 byte per bool- setidaknya di komputer saya. Anda dapat mengujinya sendiri dengan kode ini:

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

Sekarang, untuk menyusun array berdasarkan nilai, seperti Anda, dokumentasinya mengatakan:

Saat properti MarshalAsAttribute.Value disetel ke ByValArray, bidang SizeConst harus disetel untuk menunjukkan jumlah elemen dalam larik. The ArraySubTypebidang opsional dapat berisi UnmanagedTypeelemen array bila diperlukan untuk membedakan antara tipe string. Anda hanya dapat menggunakan ini UnmanagedTypepada larik yang elemennya muncul sebagai bidang dalam struktur.

Jadi kami melihat ArraySubType, dan itu memiliki dokumentasi tentang:

Anda dapat mengatur parameter ini ke nilai dari UnmanagedTypeenumerasi untuk menentukan tipe elemen array. Jika suatu jenis tidak ditentukan, jenis default yang tidak dikelola yang sesuai dengan jenis elemen array yang dikelola akan digunakan.

Sekarang lihatlah UnmanagedType, ada:

Bool
A nilai Boolean 4-byte (true! = 0, false = 0). Ini adalah tipe Win32 BOOL.

Jadi itu default untuk bool, dan itu 4 byte karena itu sesuai dengan jenis Win32 BOOL - jadi jika Anda beroperasi dengan kode yang mengharapkan BOOLlarik, itu melakukan persis apa yang Anda inginkan.

Sekarang Anda dapat menentukan ArraySubTypeas I1, yang didokumentasikan sebagai:

Integer bertanda tangan 1-byte. Anda dapat menggunakan anggota ini untuk mengubah nilai Boolean menjadi bool 1-byte, gaya-C (true = 1, false = 0).

Jadi jika kode yang Anda gunakan untuk interoperasional mengharapkan 1 byte per nilai, cukup gunakan:

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

Kode Anda kemudian akan menunjukkan bahwa mengambil 1 byte per nilai, seperti yang diharapkan.

Jon Skeet
sumber