บูลีนขนาดเท่าไหร่ใน 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

ดูเหมือนว่าจะbooleanใช้เวลา 4 ไบต์ของการจัดเก็บ เป็นการดีที่boolean จะใช้เวลาเพียงหนึ่งบิต ( falseหรือtrue, 0หรือ1, ฯลฯ .. )

เกิดอะไรขึ้นที่นี่ เป็นbooleanประเภทที่ไม่มีประสิทธิภาพจริงๆเหรอ?


7
นี่เป็นหนึ่งในการปะทะที่น่าขันที่สุดในการต่อสู้เพื่อระงับเหตุผล: จอห์นและฮันส์ตอบเพียงสองคำตอบที่ยอดเยี่ยมแม้ว่าคำตอบสำหรับคำถามนี้จะมีแนวโน้มที่จะเกือบทั้งหมดขึ้นอยู่กับความคิดเห็นมากกว่าข้อเท็จจริงการอ้างอิง หรือความเชี่ยวชาญเฉพาะด้าน
58

12
@TaW: ฉันเดาว่าคะแนนโหวตไม่ได้เกิดจากคำตอบ แต่เสียงดั้งเดิมของ OP เมื่อพวกเขาตอบคำถามเป็นครั้งแรก - พวกเขาตั้งใจที่จะเริ่มการต่อสู้อย่างชัดเจนและทันทีแสดงให้เห็นในความคิดเห็นที่ถูกลบทันที cruft ส่วนใหญ่ได้รับการกวาดใต้พรม แต่ลองดูประวัติการแก้ไขเพื่อดูสิ่งที่ฉันหมายถึง
BoltClock

1
ทำไมไม่ใช้ BitArray
อุทิศ '

คำตอบ:


242

บูลชนิดมีประวัติตาหมากรุกเข้ากันไม่ได้มีทางเลือกมากระหว่าง runtimes ภาษา สิ่งนี้เริ่มต้นด้วยตัวเลือกการออกแบบเชิงประวัติศาสตร์โดย Dennis Ritchie คนที่คิดค้นภาษา C มันไม่ได้มีบูลชนิดทางเลือกที่เป็นintที่ค่าเป็น 0 หมายถึงเท็จและความคุ้มค่าอื่น ๆ ที่ได้รับการพิจารณาความจริง

ตัวเลือกนี้ถูกยกไปข้างหน้าใน Winapi ซึ่งเป็นเหตุผลหลักในการใช้ pinvoke แต่ก็มี typedef BOOLซึ่งเป็นชื่อแทนคำสำคัญintของคอมไพเลอร์ C หากคุณไม่ได้ใช้แอตทริบิวต์ [MarshalAs] อย่างชัดเจนแล้ว C # boolจะถูกแปลงเป็น BOOL ดังนั้นจึงสร้างเขตข้อมูลที่มีความยาว 4 ไบต์

ไม่ว่าคุณจะทำอะไรการประกาศโครงสร้างของคุณจะต้องตรงกันกับตัวเลือกรันไทม์ที่ทำในภาษาที่คุณทำงานร่วมกัน เท่าที่สังเกต BOOL สำหรับ WINAPI แต่ส่วนมาก c ++ การใช้งานเลือกไบต์ส่วนใหญ่ COM อัตโนมัติ interop ใช้ VARIANT_BOOL ซึ่งเป็นระยะสั้น

จริงขนาดของ C # boolเป็นหนึ่งไบต์ เป้าหมายการออกแบบที่แข็งแกร่งของ CLR คือคุณไม่สามารถหาคำตอบได้ เลย์เอาต์เป็นรายละเอียดการใช้งานที่ขึ้นอยู่กับโปรเซสเซอร์มากเกินไป โปรเซสเซอร์นั้นค่อนข้างพิถีพิถันเกี่ยวกับประเภทของตัวแปรและการจัดเรียงตัวเลือกที่ไม่ถูกต้องอาจส่งผลกระทบต่อประสิทธิภาพการทำงานและทำให้เกิดข้อผิดพลาดขณะทำงาน ด้วยการทำให้ไม่สามารถค้นพบเลย์เอาต์. NET สามารถจัดให้มีระบบประเภทสากลที่ไม่ขึ้นอยู่กับการใช้งานจริงของรันไทม์

กล่าวอีกนัยหนึ่งคุณจะต้องจัดโครงสร้างที่รันไทม์เพื่อกำหนดเค้าโครง เวลาที่การแปลงจากเค้าโครงภายในเป็นเค้าโครง interop ทำ ซึ่งอาจเร็วมากหากรูปแบบเหมือนกันช้าเมื่อจำเป็นต้องจัดเรียงฟิลด์ใหม่เนื่องจากต้องสร้างสำเนาของโครงสร้างเสมอ คำศัพท์ทางเทคนิคสำหรับสิ่งนี้คือการแบตเตอรีการส่งผ่านโครงสร้างที่สามารถแปลงเป็นโค้ดเนมได้นั้นรวดเร็วเพราะ pinvoke marshaller สามารถส่งพอยน์เตอร์ได้

ประสิทธิภาพเป็นเหตุผลหลักที่ทำให้บูลไม่ใช่บิตเดียว มีโปรเซสเซอร์ไม่กี่ตัวที่ทำให้สามารถกำหนดแอดเดรสได้โดยตรงบิตหน่วยที่เล็กที่สุดคือไบต์ ต้องมีการเรียนการสอนเพิ่มเติมสำหรับการตกปลาบิตจากไบต์ที่ไม่ได้มาฟรี และมันก็ไม่เคยมีอะตอม

C # คอมไพเลอร์ไม่ได้มิฉะนั้นอายเกี่ยวกับการบอกคุณว่ามันใช้เวลา 1 sizeof(bool)ไบต์ใช้ นี่ยังไม่เป็นตัวทำนายที่ยอดเยี่ยมสำหรับจำนวนไบต์ของฟิลด์ที่ใช้ในขณะรันไทม์ CLR ยังต้องการใช้โมเดลหน่วยความจำ. NET และสัญญาว่าการปรับปรุงตัวแปรอย่างง่ายเป็นแบบอะตอมมิก จำเป็นต้องปรับตัวแปรให้อยู่ในหน่วยความจำอย่างเหมาะสมเพื่อให้โปรเซสเซอร์สามารถอัพเดตด้วยวงจรหน่วยความจำบัสเดียว บ่อยครั้งที่บูลต้องใช้หน่วยความจำ 4 หรือ 8 ไบต์เพราะสิ่งนี้ ช่องว่างภายในพิเศษที่เพิ่มเข้ามาเพื่อให้แน่ใจว่าสมาชิกคนต่อไปได้รับการจัดตำแหน่งอย่างเหมาะสม

CLR ใช้ประโยชน์จากรูปแบบที่ไม่สามารถค้นพบได้จริง ๆ แล้วมันสามารถปรับโครงร่างของคลาสให้เหมาะสมและจัดเรียงฟิลด์ใหม่เพื่อให้การขยายมีผลน้อยที่สุด ดังนั้นถ้าคุณมีคลาสที่มีสมาชิกบูล + int + bool มันจะใช้หน่วยความจำ 1 + (3) + 4 + 1 + (3) ไบต์ (3) เป็นระยะห่างจากกันเป็นจำนวน 12 ไบต์ ขยะ 50% เลย์เอาต์อัตโนมัติจัดเรียงเป็น 1 + 1 + (2) + 4 = 8 ไบต์ เฉพาะคลาสที่มีเลย์เอาต์อัตโนมัติ structs มีเลย์เอาต์ตามลำดับโดยค่าเริ่มต้น

ยิ่งไปกว่านั้นBoolอาจต้องการ 32 ไบต์ในโปรแกรม C ++ ที่คอมไพล์ด้วยคอมไพเลอร์ C ++ ที่ทันสมัยซึ่งรองรับชุดคำสั่ง AVX ซึ่งกำหนดความต้องการการจัดตำแหน่งแบบ 32 ไบต์ตัวแปรบูลอาจสิ้นสุดด้วยการแพ็ดดิง 31 ไบต์ นอกจากนี้เหตุผลหลักที่ทำให้. jitter. NET ไม่ปล่อยคำสั่ง SIMD นอกจากจะถูกหุ้มไว้อย่างชัดเจนแล้วจะไม่สามารถรับประกันการจัดตำแหน่งได้



2
สำหรับผู้อ่านที่สนใจ แต่ไม่รู้คุณจะชี้แจงว่าย่อหน้าสุดท้ายจริงๆควรอ่าน 32 ไบต์และไม่บิต ?
Silly Freak

3
ไม่แน่ใจว่าทำไมฉันถึงอ่านทั้งหมดนี้ (เพราะฉันไม่ต้องการรายละเอียดมากนัก) แต่มันก็น่าสนใจและเขียนได้ดี
Frank V

2
@Silly - มันเป็นไบต์ AVX ใช้ตัวแปร 512 บิตเพื่อคำนวณทางคณิตศาสตร์กับค่าทศนิยม 8 ค่าด้วยคำสั่งเดียว ตัวแปร 512 บิตดังกล่าวต้องการการจัดแนวที่ 32
Hans Passant

3
ว้าว! โพสต์เดียวให้หัวข้อมากมายที่จะเข้าใจ นั่นเป็นเหตุผลที่ฉันชอบอ่านคำถามยอดนิยม
Chaitanya Gadkari

151

ประการแรกนี่เป็นเพียงขนาดสำหรับการเชื่อมต่อ ไม่ได้แสดงขนาดในรหัสที่ได้รับการจัดการของอาร์เรย์ นั่นคือ 1 ไบต์ต่อbool- อย่างน้อยในเครื่องของฉัน คุณสามารถทดสอบด้วยตัวคุณเองด้วยรหัสนี้:

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นั่น:

บูล
4 ไบต์ค่าบูลีน (จริง! = 0 เท็จ = 0) นี่คือชนิด Win32 BOOL

นั่นคือค่าเริ่มต้นสำหรับboolและมันคือ 4 ไบต์เนื่องจากสอดคล้องกับประเภท Win32 BOOL - ดังนั้นหากคุณกำลังทำงานร่วมกับรหัสที่คาดว่า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.