web-dev-qa-db-ja.com

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

バイトとブール値の配列を持つ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

booleanは4バイトのストレージを使用しているようです。理想的には、booleanは1ビット(falseまたはtrue0または1など)のみを取ります。

ここで何が起きてるの? booleanタイプは本当に非効率的ですか?

136
biv

boolタイプには、言語ランタイム間で多くの互換性のない選択肢があるチェッカー履歴があります。これは、C言語を発明したデニスリッチーが作成した歴史的なデザインの選択から始まりました。 boolタイプはありませんでした。代替手段はintで、値0はfalseを表し、他の値はすべて考慮されましたtrue

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

何をするにしても、構造体の宣言は、相互運用する言語で行われた実行時の選択と一致する必要があります。前述のとおり、winapiのBOOLですが、ほとんどのC++実装はbyteを選択し、ほとんどのCOM Automation相互運用機能はshortであるVARIANT_BOOLを使用します。

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

つまり、実行時に構造をマーシャリングして、レイアウトを特定する必要があります。その時点で、internalレイアウトからinteropレイアウトへの変換が行われます。レイアウトが同じ場合は非常に高速になり、フィールドの再配置が必要な場合は、構造体のコピーを常に作成する必要があるため低速になります。これの専門用語はblittableです。ネイティブコードにblittable構造体を渡すのは高速です。ピンボークマーシャラーは単純にポインタを渡すことができるからです。

パフォーマンスは、boolが単一ビットではない主な理由でもあります。ビットを直接アドレス指定できるようにするプロセッサはほとんどなく、最小単位はバイトです。 extra命令は、バイトからビットを釣り上げるために必要です。これは無料ではありません。そしてそれは決してアトミックではありません。

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

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

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

233
Hans Passant

まず、これはonlyinteropのサイズです。配列のマネージコードのサイズを表していません。それは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); 
    } 
}

さて、値で配列をマーシャリングするために、あなたと同じように、 documentation は言います

MarshalAsAttribute.ValueプロパティがByValArrayに設定されている場合、SizeConstフィールドを設定して、配列内の要素の数を示す必要があります。 ArraySubTypeフィールドには、文字列タイプを区別する必要がある場合、オプションで配列要素のUnmanagedTypeを含めることができます。このUnmanagedTypeは、要素が構造体のフィールドとして表示される配列でのみ使用できます。

ArraySubType を見ると、次のドキュメントがあります:

このパラメーターをUnmanagedType列挙の値に設定して、配列の要素のタイプを指定できます。タイプが指定されていない場合、マネージドアレイの要素タイプに対応するデフォルトのアンマネージドタイプが使用されます。

UnmanagedType を見ると、次のものがあります。

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

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

これで、代わりにArraySubTypeI1として指定できます。

1バイトの符号付き整数。このメンバーを使用して、ブール値を1バイトのCスタイルbool(true = 1、false = 0)に変換できます。

したがって、相互運用しているコードが値ごとに1バイトを想定している場合は、次を使用します。

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

コードは、予想どおり、値ごとに1バイトを占めることを示します。

148
Jon Skeet