为什么我的一系列结构占用了太多的内存?
问题: 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输出(特别是VALUETYPE
和SZARRAY
types),实际上每个字节占用了84个字节! 这让我觉得太过分了! (实际上每个字节都是84个字节,因为如果我分配了512个字节,我会得到一个OutOfMemoryException
exception,我有大约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
Auto
和Sequential
没有区别。
下面是几个更多的结构和他们测量的内存分配:
// 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了一下,但找不到任何有用的东西(我真的不知道要找什么)。