C#のブール値のサイズは?本当に4バイトかかりますか?


137

バイトの配列とブール値を持つ2つの構造体があります。

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ビットだけを取る(だろうfalsetrue0あるいは1、等。)。

ここで何が起きてるの?あるboolean種類はとても非効率的な、本当に?


7
これは、ホールド理由の進行中の戦いの中で最も皮肉な衝突の1つです。ジョンとハンスによる2つの優れた答えがちょうどそれを成し遂げました。または特定の専門知識。
TaW、2015

12
@TaW:私の推測では、近い投票は回答によるものではなく、最初に質問を出したときのOPの元の調子でした-彼らは明らかに戦いを開始するつもりであり、完全に削除されたコメントでこれを明らかにしました。残骸のほとんどは敷物の下に流されてきましたが、改訂履歴をチェックして、私の意味を垣間見ることができます。
BoltClock

1
なぜBitArrayを使用しないのですか?
ded '

回答:


242

ブール型が言語ランタイムの間に多くの互換性のない選択肢と市松模様の歴史を持っています。これは、C言語を発明したデニス・リッチーによって作られた歴史的なデザインの選択から始まりました。これにはブール型がありませんでした。代わりの値はintで、値0はfalseを表し、その他の値はtrueと見なされました。

この選択は、ピンボークを使用する主な理由であるWinapiで繰り越さBOOLれました。Cコンパイラのintのエイリアスであるtypedefがあります。キーワード。明示的な[MarshalAs]属性を適用しない場合、C#ブールはBOOLに変換され、4バイト長のフィールドが生成されます。

何をする場合でも、構造体宣言は、相互運用する言語で行われたランタイムの選択と一致する必要があります。前述のように、winapiのBOOLですが、ほとんどのC ++実装はbyteを選択しました。ほとんどのCOMオートメーション相互運用機能は、VARIANT_BOOLを使用します。ます。 shortです。

C#の実際のサイズboolは1バイトです。CLRの強力な設計目標は、あなたが見つけることができないことです。レイアウトは、プロセッサーに依存しすぎる実装の詳細です。プロセッサは変数の型と配置に非常に注意深く、間違った選択はパフォーマンスに大きな影響を与え、ランタイムエラーを引き起こす可能性があります。レイアウトを検出不能にすることで、.NETは、実際のランタイム実装に依存しないユニバーサルタイプシステムを提供できます。

言い換えると、レイアウトを明確にするために、実行時に常に構造をマーシャリングする必要があります。その時点で、内部レイアウトから相互運用レイアウトへの変換が行われます。レイアウトが同じ場合は非常に高速になり、フィールドを再配置する必要がある場合は常に構造体のコピーを作成する必要があるため、遅くなる可能性があります。pinvokeマーシャラーは単純にポインターを渡すことができるため、これの専門用語はblittableであり、blittable構造体をネイティブコードに渡すのは高速です。

ブール値が単一ビットではない主な理由もパフォーマンスです。ビットを直接アドレス指定できるプロセッサはほとんどなく、最小単位はバイトです。エクストラ命令が無料で付属していませんバイトのうちのビットを魚に必要とされます。そしてそれは決してアトミックではありません。

それ以外の場合、C#コンパイラは、1バイトを使用することを恥ずかしくないので、を使用しますsizeof(bool)。これは、フィールドが実行時に取るバイト数の素晴らしい予測値ではありません。CLRも.NETメモリモデルを実装する必要があり、単純な変数の更新がアトミックであることを約束します。そのためには、メモリ内で変数を適切に配置して、プロセッサが1つのメモリバスサイクルで変数を更新できるようにする必要があります。かなり頻繁に、このため、実際にはboolはメモリに4または8バイトを必要とします。を確実にするために追加された追加のパディングメンバーが適切に配置。

CLRは実際にはレイアウトが検出されないことを利用しており、クラスのレイアウトを最適化してフィールドを再配置し、パディングを最小限に抑えることができます。したがって、bool + int + boolメンバーを持つクラスがある場合、1 +(3)+ 4 + 1 +(3)バイトのメモリが必要です。(3)はパディングで、合計12になります。バイト。50%無駄。自動レイアウトは、1 + 1 +(2)+ 4 = 8バイトに再配置されます。クラスのみに自動レイアウトがあり、構造体にはデフォルトで順次レイアウトがあります。

もっとひどいことに、AVX命令セットをサポートする最新のC ++コンパイラーでコンパイルされたC ++プログラムでは、ブール値が最大32バイトを必要とする場合があります。これは32バイトのアライメント要件を課し、bool変数は31バイトのパディングで終わる可能性があります。また、.NETジッターがSIMD命令を発行しない主な理由は、明示的にラップしない限り、アライメントの保証が得られないためです。



2
興味があるが知識のない読者のために、最後の段落がビットではなく実際に32 バイトを読み取るべきかどうかを明確にしますか?
Silly Freak

3
なぜこれだけ読んだのかはわかりませんが(これほど詳細は必要ないので)、それは魅力的でよく書かれています。
フランクV

2
@Silly- バイトです。AVXは、512ビット変数を使用して、単一の命令で8つの浮動小数点値を計算します。そのような512ビット変数は32に位置合わせが必要
ハンスアンパッサン

3
うわー!1つの投稿で、理解する必要のあるトピックが山ほどありました。だから、私はよくある質問を読むのが好きです。
Chaitanya Gadkari

151

まず、これは相互運用のサイズにすぎません。配列のマネージコードのサイズを表すものではありません。これは1バイトあたり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、あります:

Bool
4バイトのブール値(true!= 0、false = 0)。これはWin32 BOOLタイプです。

したがって、これはのデフォルトでありbool、Win32 BOOL型に対応するため4バイトです。したがって、BOOL配列を期待するコードと相互運用している場合、それは望みどおりの動作をします。

代わりにArraySubTypeas を指定できます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.