60

質問: Micro Framework は構造体の配列にメモリをどのように割り当てますか?

レプリケートするコードを含む BitBucket リポジトリ。

文脈と詳細

USB キーボードからのキーストロークの処理に遅延を挿入するために、固定サイズの配列を使用してキューを作成しています。structキーの上下イベントと遅延を表すためにa を使用しています。

public struct QueuedEvent
{
    public readonly EventType Type;        // Byte
    public readonly byte KeyPressed;
    public readonly TinyTimeSpan Delay;    // Int16

    public readonly static QueuedEvent Empty = new QueuedEvent();
}

public enum EventType : byte
{
    None = 0,
    Delay = 1,
    KeyDown = 2,
    KeyUp = 3,
    KeyPress = 4,
}

public class FixedSizeQueue
{
    private readonly QueuedEvent[] _Array;
    private int _Head = 0;
    private int _Tail = 0;

    public FixedSizeQueue(int size)
    {
        _Array = new QueuedEvent[size];
    }

    // Enqueue and Dequeue methods follow.
}

私はメモリ内で4QueuedEventバイトを占有すると考えていましたが、ガベージ コレクタ (具体的にはおよびタイプ) のデバッグ出力を見ると、実際にはそれぞれ84バイトを占有しています! これはやり過ぎだと思います!(そして、実際にはそれぞれ 84 バイトのように見えます。なぜなら、512 個を割り当てると 1 になるからです。利用可能な RAM は ~20kB なので、512 で簡単に割り当てることができるはずです)。VALUETYPESZARRAYOutOfMemoryException

質問 (再度): Micro Framework は、4 バイトに収まる構造体に 84 バイトをどのように割り当てるのですか?

ガベージ コレクターのフィギュア

これは、さまざまなサイズの配列の表ですQueuedEvent(0を割り当てたときに金額を差し引いた後):

+--------+-----------+-----------+---------+------------+-------+
| Number | VALUETYPE | B/Q'dEvnt | SZARRAY | B/Q'edEvnt | Total |
| 16     | 1152      | 72        | 192     | 12         | 84    |
| 32     | 2304      | 72        | 384     | 12         | 84    |
| 64     | 4608      | 72        | 768     | 12         | 84    |
| 128    | 9216      | 72        | 1536    | 12         | 84    |
+--------+-----------+-----------+---------+------------+-------+

数値に基づいて、フィールドが Int32 境界に合わせられているSZARRAYと推測し、 12バイトを使用しています。しかし、余分な 72 バイトがどこから来るのかわかりません。QueuedEvent

編集:Debug.GC(true)これらの数値は、デバッガー出力で取得したダンプを呼び出して観察することで取得しています。それぞれの数字が何を意味するのかを正確に特定するリファレンスは見つかりませんでした。

単純に を割り当てることができることに気付きましたint[]が、それは構造体の適切なカプセル化と型安全性を失うことを意味します。そして、構造体の真のコストがマイクロ フレームワークでどのくらいか知りたいです。


Myは、100ns ティックを表す Int64 ではなく、ミリ秒数を表すために を使用していることを除いてTinyTimeSpan、通常のものとよく似ています。TimeSpanInt16

public struct TinyTimeSpan
{
    public static readonly TinyTimeSpan Zero = new TinyTimeSpan(0);
    private short _Milliseconds;

    public TinyTimeSpan(short milliseconds)
    {
        _Milliseconds = milliseconds;
    }
    public TinyTimeSpan(TimeSpan ts)
    {
        _Milliseconds = (short)(ts.Ticks / TimeSpan.TicksPerMillisecond);
    }

    public int Milliseconds { get { return _Milliseconds; } }
    public int Seconds { get { return _Milliseconds * 1000; } }
}

ハードウェアとしてFEZ Dominoを使用しています。これがハードウェア固有である可能性は十分にあります。また、Micro Framework 4.1.

編集 - その他のテストとコメントの回答

さらに多くのテストを実行しました (今回はエミュレーターで、実際のハードウェアではありませんが、数値QueuedEventは同じであるため、ハードウェアは他のテストでも同じであると想定しています)。

レプリケートするコードを含む BitBucket リポジトリ。

次の整数型と構造体は、次のようにオーバーヘッドを引き付けませんVALUETYPE

  • バイト (1 バイト)
  • Int32 (4 バイト)
  • Int16 (2 バイト)
  • Int64 (8 バイト)
  • ダブル (8 バイト)
  • TimeSpan (12 バイト - その内部メンバーが Int64 であるため、奇妙です)
  • DateTime (12 バイト - 奇妙な)

ただし、次のGuidことを行います。それぞれ 36 バイトを使用します。

空の静的メンバーはVALUETYPE、72 バイト (配列内の同じ構造体よりも 12 バイト少ない) を使用して allocate を行います。

配列をstaticメンバーとして割り当てても、何も変わりません。

デバッグ モードまたはリリース モードで実行しても違いはありません。ただし、デバッガーを接続せずに GC デバッグ情報を取得する方法がわかりません。しかし、Micro Framework は解釈されるので、アタッチされていないデバッガーがどのような影響を与えるかはわかりません。

unsafeMicro Framework はコードをサポートしていません。また、サポートもしていません (技術的にはサポートしていますが、属性StructLayout Explicitはありません)。違いはありません。FieldOffsetStructLayout AutoSequential

以下は、さらにいくつかの構造体とそれらの測定されたメモリ割り当てです。

// Uses 12 bytes in SZARRAY and 24 in VALUETYPE, total = 36 each
public struct JustAnInt32
{
    public readonly Int32 Value;
}


// Uses 12 bytes in SZARRAY and 48 in VALUETYPE, total = 60 each
// Same as original QueuedEvent but only uses integral types.
public struct QueuedEventSimple
{
    public readonly byte Type;
    public readonly byte KeyPressed;
    public readonly short DelayMilliseconds;
    // Replacing the short with TimeSpan does not change memory usage.
}

// Uses 12 bytes in SZARRAY and 12 in VALUETYPE, total = 24 each
// I have to admit 24 bytes is a bit much for an empty struct!!
public struct Empty
{ 
}

カスタム構造体を使用するたびに、何らかのオーバーヘッドが発生するようです。構造体に何を含めても、常に 12 バイトが必要SZARRAYです。だから私はこれを試しました:

// Uses 12 bytes in SZARRAY and 36 in VALUETYPE, total = 48 each
public struct DifferentEntity
{
    public readonly Double D;
    public readonly TimeSpan T;
}

// Uses 12 bytes in SZARRAY and 108 in VALUETYPE, total = 120 each
public struct MultipleEntities
{
    public readonly DifferentEntity E1;
    public readonly DifferentEntity E2;
}

// Uses 12 bytes in SZARRAY and 60 in VALUETYPE, total = 72 each
// This is equivalent to MultipleEntities, but has quite different memory usage.
public struct TwoDoublesAndTimeSpans
{
    public readonly double D1;
    public readonly TimeSpan T1;
    public readonly double D2;
    public readonly TimeSpan T2;
}

軽微な編集

私自身の回答を投稿した後、SZARRAYアイテムごとに常に 12 バイトのオーバーヘッドがあることに気付きました。だから私はテストしましたobject[]。Micro Framework では、参照型はそれぞれ 12 バイトを消費します。

空の構造体public struct Empty { }はそれぞれ 24 バイトを消費します。

4

1 に答える 1

13

私のテストに基づいてValueTypes、デスクトップ CLR で慣れているように、Micro Framework では真の値型ではないと推測しています。少なくとも、それらは箱詰めされています。また、別のレベルの間接性も関係している可能性があります。これらのコストは、(組み込みプラットフォームではかなりの) メモリ オーバーヘッドとして発生します。

に変換しint[]ますFixedSizedQueue

実際、UInt32[]ビットバッシングをラップするためにいくつかの拡張メソッドを使用して追加しました。

ソースコードを少し調べてみましたが、役に立つものは何も見つかりませんでした (そして、何を探すべきか本当にわかりません)。

于 2012-09-16T12:54:35.913 に答える