为什么我的一系列结构占用这么多的内存?
问题: Micro Framework如何为结构数组分配内存?
带有代码复制的BitBucket存储库。
上下文和细节
我正在使用固定大小的阵列进行队列插入处理来自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个字节,但基于查看垃圾收集器的调试输出(特别是VALUETYPE
和SZARRAY
类型),它实际上每个字节占用84个字节! 这让我感到不可思议! (实际上每个字节看起来都是84个字节,因为如果我分配了512个字节,我会得到一个OutOfMemoryException
异常,因为我有大约20kB的RAM空间,所以我应该能够轻松地分配512个字节)。
问题(再次): Micro Framework如何管理为可以适合4的结构分配84个字节?
垃圾收集器数字
这里有一个不同大小的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
字段正在与Int32边界对齐,因此占用了12个字节。 但我不知道额外的72个字节来自哪里。
编辑:我通过调用Debug.GC(true)
并观察我在调试器输出中获得的转储得到这些数字。 我还没有找到一个确切标识每个数字的含义的参考。
我意识到我可以简单地分配一个int[]
,但这意味着我失去了良好的封装和结构的任何类型安全。 我真的很想知道微架构中结构的真实成本。
我的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。
编辑 - 更多的测试和评论的答案
我跑了一大堆更多的测试(这次在模拟器中,不是在真正的硬件上,但QueuedEvent
的数字是相同的,所以我假设我的硬件在其他测试中是相同的)。
带有代码复制的BitBucket存储库。
以下整体类型和结构不会像VALUETYPE
那样吸引任何开销:
但是, Guid
确实:每个使用36个字节。
空的静态成员确实分配VALUETYPE
,使用72个字节(比数组中的同一个结构少12个字节)。
将数组分配为static
成员不会改变任何内容。
在Debug或Release模式下运行没有区别。 我不知道如何在没有调试器的情况下获取GC调试信息。 但是Micro Framework被解释了,所以我不知道非连接的调试器会有什么影响。
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
{
}
看来每次我使用自定义结构时,都会产生一些开销。 无论我在结构中包含什么, 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;
}
轻微编辑
在发布我自己的答案后,我意识到每个项目在SZARRAY
始终有12个字节的开销。 所以我测试了一个object[]
。 参考类型在Micro Framework中每个消耗12个字节。
一个空的struct public struct Empty { }
每个消耗24个字节。
根据我的测试,我猜测Micro Framework中的ValueTypes
不像我们习惯在桌面CLR上那样是真正的值类型。 至少,他们正在装盒。 而且还可能存在另一层级的间接性。 这些成本发生在(对于嵌入式平台而言相当大的)内存开销。
我将在我的FixedSizedQueue
转换为int[]
。
实际上,我最终使用了UInt32[]
并添加了一些扩展方法来包装位。
我在源代码中搜索了一下,但找不到任何有用的东西(我真的不知道要找什么)。
链接地址: http://www.djcxy.com/p/28059.html