この記事は機械翻訳のミラー記事です。元の記事にジャンプするにはこちらをクリックしてください。

眺める: 537|答える: 1

[出典] [ターン]。 NET/C#はどのようにしてインスタンスがどれだけのメモリを占有しているかを計算しているのですか?

[リンクをコピー]
2025年8月27日 09:00:22に投稿 | | | |
CPUとメモリがプログラムにとって最も重要な2つの指標であることは誰もが知っていますが、そこでどれだけの人が「ある型(値型または参照型)のインスタンスはメモリ上で何バイトを占めるのか?」という問いについて真剣に考えたことがあるでしょうか? 多くの人が答えられない。 C#はサイズ計算のための演算子やAPIを提供していますが、どれも先ほど質問した問題を完全に解決するものではありません。 この記事では、値型や参照型のインスタンスが占有するメモリバイト数を計算する方法を提供します。 ソースコードはここからダウンロードできます。

1. 演算子のサイズ
2. マーシャル。サイズ 方法
3. 安全でない。方法>のサイズ
4. フィールドメンバーの種類に基づいて計算できますか?
5. 値型およびアプリケーション型のレイアウト
6. LDFLDA指令
7. 値型のバイト数を計算する
8. 引用タイプのバイト数を数える
9. 完全な計算

1. 演算子のサイズ

演算サイズは、ある型のインスタンスが占有するバイト数を決定するために使われますが、管理されていない型にのみ適用可能です。 いわゆる管理されていないタイプは以下に限定されます:

プリミティブ型:ブール、バイト、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、IntPtr、UIntPtr、Char、Double、Single)
十進分類
列挙型
ポインタタイプ
管理されていない型のデータメンバーのみを含む構造体
名前が示す通り、管理されていないタイプは値タイプであり、対応するインスタンスは管理対象オブジェクトへの参照を含めません。 このようにサイズの演算子を呼び出す一般的なメソッドを定義すると、汎用パラメータTは未定義の制約と安全でないタグをメソッドに追加しなければなりません。

ネイティブ型とenum型のみが演算子のサイズを直接使用でき、他の型(ポインタやカスタム構造体)に適用する場合は追加しなければなりません。/unsafeコンパイルタグ、そして危険だ文脈の中で。

次の構造体Foobarは管理されていない型ではないため、プログラムはコンパイルエラーを発生します。

2. マーシャル。サイズ 方法

静的型Marshalは、管理されていないメモリの割り当てとコピー、管理型と非管理型の変換、そして管理されていないメモリに対する一連の操作を行うのに役立つ一連のAPIを定義しています(計算科学におけるMarshalとは、データ保存や転送のためのメモリオブジェクトを対応する形式に変換する操作を指します)。 静的(static)は、以下の4つのSizeOfメソッドオーバーロードを含み、特定の型やオブジェクトのバイト数を決定するためのものです。

Marshal.SizeOf メソッドは、管理されていない型の指定型に制限はありませんが、それでも指定が必要です価値タイプ。 もし入ってくるオブジェクトがオブジェクトであれば、それは値型のボックスでなければなりません。

次のフーバーは次のように定義されます:種類両方のSizeOfメソッドへの呼び出しはArgumentException例外とプロンプトを投げます:型「Foobar」は管理されていない構造体としてマーシャルできません; 意味のあるサイズやオフセットは計算できません。

マーシャル。サイズ 方法ジェネリックはサポートされていませんしかし、支持を支える構造の配置に関する要件も存在しますシーケンシャルそしてレイアウトモード。 以下に示すFoobar構造体はAutoレイアウトモードを採用しており(Autoは、非管理環境でのメモリ配置要件が厳しくなるため、フィールドメンバーに基づくメモリレイアウトの「動的計画」をサポートしません)、SizeOfメソッドを呼び出しても上記と同じArgumentException例外が投げられます。

3. 安全でない。サイズの方法

Static Unsafeは管理されていないメモリに対してより多くの低レベル操作を提供し、同様のSizeIOfメソッドもこのタイプで定義されています。 このメソッドには指定された型に制限はありませんが、参照型を指定するとポインタバイト数「(IntPtr.Size)。

4. フィールドメンバーの種類に基づいて計算できますか?

値型と参照型の両方が連続的な断片としてマッピングされている(またはレジスタに直接保存される)ことがわかっています。 型の目的はオブジェクトのメモリ配置を指定することであり、同じタイプのインスタンスは同じレイアウトを持ち、バイト数も自然に同じです(参照型フィールドの場合は、このバイトシーケンス内の参照アドレスのみを格納します)。 バイト長は型によって決まるので、各フィールドメンバーの型を特定できれば、その型に対応するバイト数を計算できるのではないでしょうか? 実際、それは不可能だ。

例えば、バイト、ショート、整数、ロングのバイトは1、2、4、8なので、バイトバイナリのバイト数は2になりますが、バイト+ショート、バイト+整数、バイト+ロングの型の組み合わせでは、対応するバイトは3、5、9ではなく3、8、16となります。 なぜなら、これは記憶の整合の問題に関わるからです。

5. 値型および参照型の配置

参照型とサブタイプのインスタンスが占有するバイト数も、まったく同じデータメンバーでも異なります。 以下の画像に示すように、値型インスタンスのバイト列全員が保管に使われるフィールドメンバーです。 参照型のインスタンスでは、対応するメソッドテーブルのアドレスもフィールドバイト列の前に格納されます。 メソッドテーブルは型を表すほぼすべてのメタデータを提供しており、この参照を使ってインスタンスがどの型に属するかを判定します。 最前面には追加のバイトもあり、ここではこれを「Extra bytes(1000 200 200 100 200 100 By)」と呼びますオブジェクトヘッダーこれはオブジェクトのロック状態を格納するだけでなく、ハッシュ値をここでキャッシュすることもできます。 参照型変数を作成すると、この変数はこれはインスタンスが占有するメモリの最初のバイトを指すのではなく、メソッドテーブルのアドレスが格納されている場所を示します



6. LDFLDA指令

前述の通り、サイズオブ演算子と静的型Marshal/Unsafeが提供するSizeOfメソッドでは、インスタンスが占めるバイト長の計算を解くことはできません。 私の知る限り、この問題はC#フィールドだけでは解決できませんが、ILレベルで提供されていますLdflda説明書があればこの問題の解決に役立ちます。 名前の通り、LdfldaはLoad Field Addressの略で、インスタンス内のフィールドアドレスを取得するのに役立ちます。 このIL命令はC#に対応するAPIがないため、IL Emitを用いて以下の形でのみ使用できます。

上記のコードスニペットに示されているように、SizeCalculator型のGenerateFieldAddressAccessorメソッドがあり、指定されたタイプのフィールドのリストに基づいてFunc<object?(long[]>型のデリゲートを生成し、指定されたオブジェクトとそのすべてのフィールドのメモリアドレスを返すのに役立ちます。 オブジェクト自体のアドレスと各フィールドのアドレスを組み合わせることで、自然に各フィールドのオフセットを得て、インスタンス全体が占有するメモリバイト数を簡単に計算できます。

7. 値型のバイト数を計算する

値型と参照型はメモリ上のレイアウトが異なるため、異なる計算も必要です。 構造体のバイトはメモリ内のすべてのフィールドの内容なので、巧妙な計算方法を用います。 例えば、型Tの構造体のバイト数を決定する必要があると仮定し、ValueTuple<T,T>タプルを作成し、その2番目のフィールドItem2のオフセットはstruct Tのバイト数となります。 具体的な計算方法は、以下のCalculateValueTypeInstanceメソッドに反映されています。

上記のコードスニペットのように、計算すべきstruct型がTであると仮定すると、GetDefaultAsObjectメソッドを呼び出してデフォルト(T)オブジェクトを反射の形で取得し、ValueTuple<T,T>タプルを作成します。 インスタンスとそのフィールドアドレスを計算するために Func<object?, long[]> delegate を取得するために GenerateFieldAddressAccessor メソッドを呼び出した後、この delegate を引数と呼びます。 得られる3つのメモリアドレスについて、コードタプルとフィールド1と2のアドレスは同じで、Item2から最初のアドレスを引いた3番目のアドレスを使い、望む結果が得られます。

8. 引用タイプのバイト数を数える

参照型のバイト計算はより複雑で、この考え方を用います。インスタンス自体と各フィールドのアドレスを取得した後、最後のフィールドのオフセットを得るためにアドレスをソートします。 このオフセットを最後のフィールド自体のバイト数に加え、必要な「最初と最後のバイト」を加えます。これは次のCalculateReferneceTypeInstanceメソッドに反映されます。

上記のコードスニペットに示されているように、指定された型にフィールドが定義されていない場合、CalculateReferneceTypeInstanceは参照型インスタンスの最小バイト数、すなわちアドレスポインタバイト数の3倍を返します。 x86アーキテクチャでは、アプリケーションタイプオブジェクトは少なくとも12バイトを占め、ObjectHeader(4バイト)、メソッドテーブルポインタ(バイト)、およびフィールドコンテンツの少なくとも4バイト(この4バイトは、フィールドなしで定義されていない型がなくても必要です)を含みます。 x64アーキテクチャの場合、この最小バイト数は24になります。なぜならメソッドテーブルポインタと最小フィールド内容が8バイトになるためです。ただし、ObjectHeaderの有効な内容は4バイトしか占有しませんが、前方に4バイトのパディングが追加されます。

最後のフィールドが占有するバイトの決済も非常にシンプルです。もし型が値型であれば、先に定義したCalculateValueTypeInstanceメソッドを呼び出して計算します。参照型であれば、フィールドに格納される内容は対象オブジェクトのメモリアドレスのみなので、長さはIntPtr.Sizeとなります。 参照型インスタンスはデフォルトでメモリ内のIntPtr.Sizeにアライメントされているため、ここでも同様の処理が行われます。 最後に、参照型インスタンスの参照はメモリの最初のバイトではなく、メソッドテーブルポインタを格納するバイトを指すため、ObjecthHeader(IntPtr.Size)のバイト数を加える必要があります。

9. 完全な計算

値型と参照型インスタンスのバイト数を計算するために用いられる2つの方法は、以下のSizeOfメソッドで使用されます。 Ldflda命令の呼び出しは対応するインスタンスを提供する必要があるため、このメソッドはターゲット型を提供するだけでなく、対応するインスタンスを取得するデゲイトも提供します。 この代理者に対応するパラメータはデフォルトにでき、値型にはデフォルト値を使います。 参照型については、デフォルトのコンストラクタを使ってターゲットオブジェクトを作成することも試みます。 このデリゲートオブジェクトが提供されておらず、ターゲットインスタンスを作成できない場合、SizeOfメソッドは例外をスローします。 ターゲットインスタンスを提供する必要がありますが、計算結果は型にのみ関連しているため、計算結果をキャッシュします。 呼びやすさのために、もう一つの汎用的なSizeOfメソッドも提供<T>しています。

以下のコードスニペットでは、同じフィールド定義を持つ2つの構造体と型のバイト数を出力するために使っています。 次回の記事では、計算されたバイト数に基づいてメモリ内のインスタンスの全バイナリ内容をさらに公開しますので、ご期待ください。

元のリンク:ハイパーリンクのログインが見えます。




先の:フロントエンドフレームワークはComponent-Partyオープンソースプロジェクトを学習します
次に:MinIOストレージ(iii)ローカルファイルをminioバケットにコピーアップロード(移行)する
 地主| 2025年8月27日 09:33:22に投稿 |
C#コレクションは10,000件のデータを挿入し、メモリを占有します




コード:


免責事項:
Code Farmer Networkが発行するすべてのソフトウェア、プログラミング資料、記事は学習および研究目的のみを目的としています。 上記の内容は商業的または違法な目的で使用されてはならず、そうでなければ利用者はすべての結果を負うことになります。 このサイトの情報はインターネットからのものであり、著作権紛争はこのサイトとは関係ありません。 ダウンロード後24時間以内に上記の内容を完全にパソコンから削除してください。 もしこのプログラムを気に入ったら、正規のソフトウェアを支持し、登録を購入し、より良い本物のサービスを受けてください。 もし侵害があれば、メールでご連絡ください。

Mail To:help@itsvse.com