で野田時間 v2では、我々はナノ秒の解像度に移動しています。つまり、関心のある時間の範囲全体を表すのに8バイト整数を使用できなくなったということです。そのため、野田時間の(多くの)構造体のメモリ使用量を調査するようになりました。 CLRの配置の決定におけるわずかな奇妙さを明らかにするため。
まず、これは実装の決定であり、デフォルトの動作はいつでも変更される可能性があることを理解しています。とを使用して変更できることを理解していますが、可能であればそれを必要としないソリューションを考え出します。[StructLayout]
[FieldOffset]
私の中心的なシナリオはstruct
、参照タイプのフィールドと他の2つの値タイプのフィールドを含むaがあり、これらのフィールドがint
。。私はそれが64ビットCLRで16バイトとして表されることを期待していましたが(参照用に8バイト、他のそれぞれに4バイト)、何らかの理由で24バイトを使用しています。ちなみに私は配列を使用してスペースを測定しています-レイアウトは状況によって異なる場合があることは理解していますが、これは妥当な出発点のように感じました。
これは問題を示すサンプルプログラムです:
using System;
using System.Runtime.InteropServices;
#pragma warning disable 0169
struct Int32Wrapper
{
int x;
}
struct TwoInt32s
{
int x, y;
}
struct TwoInt32Wrappers
{
Int32Wrapper x, y;
}
struct RefAndTwoInt32s
{
string text;
int x, y;
}
struct RefAndTwoInt32Wrappers
{
string text;
Int32Wrapper x, y;
}
class Test
{
static void Main()
{
Console.WriteLine("Environment: CLR {0} on {1} ({2})",
Environment.Version,
Environment.OSVersion,
Environment.Is64BitProcess ? "64 bit" : "32 bit");
ShowSize<Int32Wrapper>();
ShowSize<TwoInt32s>();
ShowSize<TwoInt32Wrappers>();
ShowSize<RefAndTwoInt32s>();
ShowSize<RefAndTwoInt32Wrappers>();
}
static void ShowSize<T>()
{
long before = GC.GetTotalMemory(true);
T[] array = new T[100000];
long after = GC.GetTotalMemory(true);
Console.WriteLine("{0}: {1}", typeof(T),
(after - before) / array.Length);
}
}
そして私のラップトップでのコンパイルと出力:
c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.
c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24
そう:
- 参照型フィールドがない場合、CLRは
Int32Wrapper
フィールドをまとめてパックします(TwoInt32Wrappers
サイズは8)。 - 参照タイプのフィールドがあっても、CLRは引き続きパックできます
int
フィールドを一緒ます(RefAndTwoInt32s
サイズは16です)。 - 2つを組み合わせると、それぞれ
Int32Wrapper
フィールドは8バイトにパディング/整列されているように見えます。(RefAndTwoInt32Wrappers
サイズは24です。) - デバッガーで同じコードを実行すると(ただし、まだリリースビルドです)、サイズは12と表示されます。
他のいくつかの実験でも同様の結果が得られています。
- 値型フィールドの後に参照型フィールドを配置しても役に立たない
- の
object
代わりに使用string
しても役に立たない(私はそれが「任意の参照型」であることを期待しています) - 参照の「ラッパー」として別の構造体を使用しても役に立たない
- 参照のラッパーとしてジェネリック構造体を使用しても役に立たない
- フィールドを追加し続けても(簡単にするためにペアで)、
int
フィールドは引き続き4バイトとしてカウントされます。Int32Wrapper
カウントされフィールドは8バイトとしてカウントされます [StructLayout(LayoutKind.Sequential, Pack = 4)]
目に見えるすべての構造体に追加しても結果は変わりません
誰かがこれについて(理想的にはリファレンスドキュメントを使用して)説明したり、定数のフィールドオフセットを指定せずにフィールドをパックしたいというCLRへのヒントを得る方法についての提案はありますか?
TwoInt32Wrappers
、またはan Int64
とa を持つ構造体を作成するとTwoInt32Wrappers
どうなりますか?ジェネリックPair<T1,T2> {public T1 f1; public T2 f2;}
を作成してPair<string,Pair<int,int>>
から作成するとPair<string,Pair<Int32Wrapper,Int32Wrapper>>
どうですか?JITterがパディングを強制するのはどの組み合わせですか?
Pair<string, TwoInt32Wrappers>
しか与えないので、この問題に対処できます。魅力的です。
Marshal.SizeOf
ネイティブコードに渡される構造のサイズを返します。これは、.NETコードの構造のサイズとは何の関係もありません。
Ref<T>
が、string
代わりに使用しています。違いを生むはずではありません。