为什么我的一系列结构占用了太多的内存?

问题: Micro Framework如何为结构数组分配内存?

带有代码复制的BitBucket存储库 。

上下文和细节

我正在使用一个固定大小的arrays来插入延迟来处理来自USB键盘的按键。 我正在使用一个struct来表示关键的向上和向下事件和延迟。

 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. } 

我想我的QueuedEvent会在内存中占用4个字节,但是根据查看垃圾收集器的debugging输出(特别是VALUETYPESZARRAYtypes),实际上每个字节占用了84个字节! 这让我觉得太过分了! (实际上每个字节都是84个字节,因为如果我分配了512个字节,我会得到一个OutOfMemoryExceptionexception,我有大约20kB的RAM空间,所以我应该能够很容易地分配512个字节)。

问题(再次): Micro Framework如何pipe理分配84个字节的结构,可以适应4?

垃圾收集器的数字

这里是QueuedEvent的不同大小的数组的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 | +--------+-----------+-----------+---------+------------+-------+ 

基于SZARRAY数字,我猜我的QueuedEvent字段被alignment到Int32边界,因此占用了12个字节。 但是我不知道额外的72个字节来自哪里。

编辑:我通过调用Debug.GC(true)并观察我在debugging器输出中获得的转储得到这些数字。 我没有find一个参考,确切地确定每个数字的含义。

我意识到我可以简单地分配一个int[] ,但这意味着我失去了良好的封装和结构的任何types的安全。 我真的很想知道微架构中结构的真实成本是多less。


我的TinyTimeSpan非常像一个普通的TimeSpan除了使用Int16代表毫秒数而不是代表100ns的Int64。

 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。

编辑 – 更多的testing和评论的答案

我跑了一大堆更多的testing(这次在模拟器中,不是真正的硬件,但QueuedEvent的数字是相同的,所以我假设我的硬件将是相同的其他testing)。

带有代码复制的BitBucket存储库 。

以下整体types和结构不会像VALUETYPE那样吸引任何开销:

  • 字节(1字节)
  • Int32(4字节)
  • Int16(2字节)
  • Int64(8字节)
  • 双(8字节)
  • TimeSpan(12字节 – 奇怪,因为它的内部成员是Int64)
  • DateTime(12字节 – 奇怪)

但是, Guid确实:每个使用36个字节。

空的静态成员不分配VALUETYPE ,使用72个字节(比数组中的相同结构less12个字节)。

将数组分配为static成员不会改变任何内容。

在Debug或Release模式下运行没有区别。 我不知道如何获得没有附加debugging器的GCdebugging信息。 但是Micro Framework被解释了,所以我不知道一个非连接的debugging器会有什么影响。

Micro Framework不支持unsafe代码。 它也不支持StructLayout Explicit (从技术上说,它确实没有,但是没有FieldOffset属性)。 StructLayout 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 { } 

看来每次我使用一个自定义结构,我招致了一些开销。 不pipe我在结构中包含什么,在SZARRAY总是需要12个字节。 所以我试过这个:

 // 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; } 

轻微编辑

发布我自己的答案后,我意识到每个项目总是有12个字节的SZARRAY开销。 所以我testing了一个object[] 。 参考types在Micro Framework中每个消耗12个字节。

一个空的struct public struct Empty { }每个消耗24个字节。

根据我的testing,我猜测Micro Framework中的ValueTypes不像我们习惯的桌面CLR那样是真正的值types。 至less,他们正在装盒。 而且还可能有另一个间接的层面。 这些成本是在(对于embedded式平台来说相当大的)内存开销中产生的。

我将在我的FixedSizedQueue转换为int[]

实际上,我最终使用了UInt32[]并添加了一些扩展方法来包裹位。

我在源代码中search了一下,但找不到任何有用的东西(我真的不知道要找什么)。