为什么我的结构数组占用这么多内存?

时间:2022-03-06 15:36:41

Question: How does the Micro Framework allocate memory for an array of structs?

问:微框架如何为一组结构体分配内存?

BitBucket repository with code to replicate.

带要复制的代码的位桶存储库。

Context and Detail

I'm making a queue using a fixed sized array to insert delays in processing keystrokes from a USB Keyboard. I'm using a struct to represent the key up and down events and the delay.

我正在制作一个队列,使用一个固定大小的数组来插入从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.
}

I would have thought my QueuedEvent would occupy 4 bytes in memory, but, based on looking at the debug output of the garbage collector (specifically the VALUETYPE and SZARRAY types) it is actually taking up 84 bytes each! This strikes me as overkill! (And it really appears to be 84 bytes each, because I get an OutOfMemoryException if I allocate 512 of them. I have ~20kB of RAM available, so I should be able to allocate at 512 easily).

我本以为QueuedEvent在内存中会占用4个字节,但是,根据垃圾收集器的调试输出(特别是VALUETYPE和SZARRAY类型),它实际上每个都要占用84个字节!我觉得这太过分了!(实际上,每个字节都有84个字节,因为如果我分配512个字节,就会得到OutOfMemoryException。我有大约20kB的RAM可用,所以我应该能够轻松地在512分配内存)。

Question (again): How does the Micro Framework manage to allocate 84 bytes for a struct which could fit in 4?

问题(再次):微框架如何为一个能容纳4个字节的结构体分配84个字节?

Garbage Collector Figures

Here's a table of different sized arrays of QueuedEvent (after I subtract the amounts when I allocate 0):

这是一个不同大小的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    |
+--------+-----------+-----------+---------+------------+-------+

Based on the SZARRAY numbers, I'd guess my QueuedEvent fields are being aligned to Int32 boundaries, thus taking up 12 bytes. But I have no idea where the extra 72 bytes come from.

根据SZARRAY编号,我猜测QueuedEvent字段被对齐到Int32边界,因此占用了12个字节。但是我不知道额外的72字节来自哪里。

Edit: I'm getting these numbers by calling Debug.GC(true) and observing the dump I get in my debugger output. I have not found a reference which identifies exactly what each of the numbers mean.

编辑:我通过调用Debug.GC(true)和观察调试器输出中的转储来获取这些数字。我还没有找到一个参考书,可以准确地指出每一个数字的含义。

I realise I could simply allocate an int[], but that means I lose the nice encapsulation and any type safety of the struct. And I'd really like to know what the true cost of a struct is in the micro framework.

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


My TinyTimeSpan is much like a regular TimeSpan except is using an Int16 to represent a number of milliseconds rather than an Int64 representing 100ns ticks.

我的TinyTimeSpan很像普通的TimeSpan,只不过它使用Int16表示毫秒数,而不是Int64表示100ns的节拍数。

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

I'm using a FEZ Domino as hardware. It's totally possible this is hardware specific. Also, Micro Framework 4.1.

我使用FEZ Domino作为硬件。这完全有可能是硬件专用的。同时,微Framework 4.1。

Edit - More Testing And Comment Answers

I ran a whole bunch more tests (in the emulator this time, not on real hardware, but the numbers for QueuedEvent are the same, so I'm assuming my hardware would be identical for other tests).

我运行了大量的测试(这次在模拟器中,不是在真正的硬件上,但是QueuedEvent的数字是相同的,所以我假设我的硬件对于其他测试是相同的)。

BitBucket repository with code to replicate.

带要复制的代码的位桶存储库。

The following integral types and structs do not attract any overhead as VALUETYPE:

以下的整体类型和结构不会作为VALUETYPE引起任何开销:

  • Byte (1 byte)
  • 字节(1个字节)
  • Int32 (4 bytes)
  • Int32(4字节)
  • Int16 (2 bytes)
  • Int16(2字节)
  • Int64 (8 bytes)
  • Int64(8个字节)
  • Double (8 bytes)
  • 双(8个字节)
  • TimeSpan (12 bytes - strange, as its internal member is an Int64)
  • TimeSpan(12字节——奇怪,因为它的内部成员是Int64)
  • DateTime (12 bytes - strange)
  • DateTime(12字节- strange)

However, Guid does: each using 36 bytes.

但是,Guid做了:每一个都使用36个字节。

The empty static member does allocate VALUETYPE, using 72 bytes (12 bytes less than the same struct in an array).

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

Allocating the array as a static member does not change anything.

将数组分配为静态成员不会改变任何东西。

Running in Debug or Release modes makes no difference. I don't know how to get the GC debug info without a debugger attached though. But Micro Framework is interpreted, so I don't know what effect a non-attached debugger would have anyway.

在调试或发布模式下运行没有区别。我不知道如何在没有附带调试器的情况下获得GC调试信息。但是微框架是解释的,所以我不知道一个不附带的调试器会产生什么效果。

Micro Framework does not support unsafe code. Nor does it support StructLayout Explicit (well, technically it does, but there is no FieldOffset attribute) . StructLayout Auto and Sequential make no difference.

微框架不支持不安全的代码。它也不支持显式的StructLayout(技术上它支持,但是没有FieldOffset属性)。结构布局自动和顺序没有区别。

Here are are few more structs and their measured memory allocation:

这里有更多的结构和他们的测量内存分配:

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

It seems every time I use a custom struct, I incur some sort of overhead. And no matter what I include in the struct, it always requires 12 bytes in SZARRAY. So I tried this:

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

Minor Edit

After posting my own answer, I realised there was always a 12 byte overhead in SZARRAY per item. So I tested an object[]. Reference types consume 12 bytes each in the Micro Framework.

在发布了我自己的答案之后,我意识到每个条目在SZARRAY中总有12字节的开销。所以我测试了一个对象[]。参考类型在微框架中每个字节消耗12个字节。

An empty struct public struct Empty { } consumes 24 bytes each.

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

1 个解决方案

#1


13  

Based on my tests, I'm guessing ValueTypes in the Micro Framework are not true value types as we're used to on the desktop CLR. At the very least, they are being boxed. And there may be another level of indirection involved too. These costs are incurred in a (quite substantial for an embedded platform) memory overhead.

根据我的测试,我猜微框架中的ValueTypes并不是我们在桌面CLR中习惯的真正的值类型。至少,他们是被限制的。也许还有另一个层面的间接影响。这些成本在内存开销(对于嵌入式平台来说相当大)中产生。

I'll be converting to an int[] in my FixedSizedQueue .

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

Actually, I ended up using UInt32[] and added some extension methods to wrap around the bit bashing.

实际上,我最后使用了UInt32[]并添加了一些扩展方法来结束对比特的攻击。

I poked around a bit in the source code, but couldn't find anything helpful (and I really don't know what to look for either).

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

#1


13  

Based on my tests, I'm guessing ValueTypes in the Micro Framework are not true value types as we're used to on the desktop CLR. At the very least, they are being boxed. And there may be another level of indirection involved too. These costs are incurred in a (quite substantial for an embedded platform) memory overhead.

根据我的测试,我猜微框架中的ValueTypes并不是我们在桌面CLR中习惯的真正的值类型。至少,他们是被限制的。也许还有另一个层面的间接影响。这些成本在内存开销(对于嵌入式平台来说相当大)中产生。

I'll be converting to an int[] in my FixedSizedQueue .

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

Actually, I ended up using UInt32[] and added some extension methods to wrap around the bit bashing.

实际上,我最后使用了UInt32[]并添加了一些扩展方法来结束对比特的攻击。

I poked around a bit in the source code, but couldn't find anything helpful (and I really don't know what to look for either).

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