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

问题: 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个字节,但基于查看垃圾收集器的调试输出(特别是VALUETYPESZARRAY类型),它实际上每个字节占用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那样吸引任何开销:

  • 字节(1字节)
  • Int32(4字节)
  • Int16(2字节)
  • Int64(8字节)
  • 双(8字节)
  • TimeSpan(12字节 - 奇怪,因为它的内部成员是Int64)
  • 日期时间(12个字节 - 奇怪)
  • 但是, Guid确实:每个使用36个字节。

    空的静态成员确实分配VALUETYPE ,使用72个字节(比数组中的同一个结构少12个字节)。

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

    在Debug或Release模式下运行没有区别。 我不知道如何在没有调试器的情况下获取GC调试信息。 但是Micro Framework被解释了,所以我不知道非连接的调试器会有什么影响。

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

    看来每次我使用自定义结构时,都会产生一些开销。 无论我在结构中包含什么, 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

    上一篇: Why do my array of structs take up so much memory?

    下一篇: Why are mutable structs “evil”?