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

标签 c# .net memory-management out-of-memory .net-micro-framework

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

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 内存中的字节数,但是,根据垃圾收集器(特别是 VALUETYPESZARRAY 类型)的调试输出,它实际上占用了 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 :
  • 字节(1 字节)
  • Int32(4 字节)
  • Int16(2 个字节)
  • Int64(8 字节)
  • double (8 字节)
  • TimeSpan(12 字节 - 奇怪,因为它的内部成员是 Int64)
  • 日期时间(12 字节 - 奇怪)

  • 然而,Guid确实:每个使用 36 个字节。

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

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

    在 Debug 或 Release 模式下运行没有区别。我不知道如何在没有附加调试器的情况下获取 GC 调试信息。但是微框架是解释的,所以我不知道非附加调试器会产生什么影响。

    微框架不支持 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
    { 
    }
    

    似乎每次我使用自定义结构时,都会产生某种开销。无论我在结构中包含什么,它总是需要 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/

    相关文章:

    c# - 通过 HttpClient 发布文件时 HttpRequest.Files 为空

    c# - SNIReadSyncOverAsync 和 WaitForSingleObject 阻塞 EF 性能?

    c# - LINQ to Entities 无法识别方法 IsUserInCc

    c - mmap 请求的内存大小对可用内存有什么影响?

    c++ - 内存地址显示? C++

    c# - 如何获取 Stream 的底层文件句柄?

    c# - 使用 HtmlAgilityPack 确定字符串是否仅包含允许标签列表中的标签

    c# - 为什么在 C# 中使用泛型约束

    .net - 如何防止线程中的无限循环消耗所有CPU资源?

    javascript - 清除 Tampermonkey 的浏览器选项卡 RAM 使用情况吗?