问题:微框架如何为结构数组分配内存?
BitBucket repository带有要复制的代码。
背景和细节
我正在使用固定大小的数组创建一个队列,以在处理来自 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
不同大小数组的表格(在我分配 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作为硬件。这完全有可能是特定于硬件的。此外,微框架 4.1。
编辑 - 更多测试和评论答案
我运行了更多的测试(这次是在模拟器中,而不是在真实硬件上,但
QueuedEvent
的数字是相同的,所以我假设我的硬件在其他测试中是相同的)。BitBucket repository带有要复制的代码。
以下整数类型和结构不会产生任何开销,如
VALUETYPE
:然而,
Guid
确实:每个使用 36 个字节。空的静态成员确实分配了
VALUETYPE
, 使用 72 个字节(比数组中的相同结构少 12 个字节)。将数组分配为
static
成员不会改变任何东西。在 Debug 或 Release 模式下运行没有区别。我不知道如何在没有附加调试器的情况下获取 GC 调试信息。但是微框架是解释的,所以我不知道非附加调试器会产生什么影响。
微框架不支持
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
{
}
似乎每次我使用自定义结构时,都会产生某种开销。无论我在结构中包含什么,它总是需要 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 个字节。
最佳答案
根据我的测试,我猜 ValueTypes
Micro Framework 中的值类型不是我们在桌面 CLR 上习惯的真正值类型。至少,他们正在被装箱。并且可能还涉及另一个级别的间接性。这些成本产生于(对于嵌入式平台来说相当可观)内存开销。
我将转换为 int[]
在我的 FixedSizedQueue
.
实际上,我最终使用了 UInt32[]
并添加了一些扩展方法来解决bit bashing。
我在 source code 中戳了一下,但找不到任何有用的东西(我也不知道该找什么)。
关于c# - 为什么我的结构数组占用了这么多内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12445185/