c# - لماذا تأخذ مجموعة الهياكل الخاصة بي الكثير من الذاكرة؟



.net memory-management (1)

السؤال: كيف يقوم 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 بايت في الذاكرة ، ولكن ، استنادا إلى النظر في إخراج debug من جامع القمامة (على وجه التحديد أنواع SZARRAY و SZARRAY ) أنها تأخذ بالفعل 84 بايت لكل! هذا يضربني مبالغة! (ويبدو بالفعل أن 84 بايت لكل منهما ، لأنني أحصل على OutOfMemoryException إذا قمت بتخصيص 512 منهم. لدي ~ 20kB من ذاكرة الوصول العشوائي المتاحة ، لذلك ينبغي أن أكون قادراً على تخصيص 512 بسرعة).

السؤال (مرة أخرى): كيف يمكن لإدارة Micro Framework تخصيص 84 بايت للبنية التي يمكن وضعها في 4؟

شخصيات جامع القمامة

إليك جدول صفائف مختلفة الحجم من 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[] ، ولكن هذا يعني أنني أفقد التغليف الجميل وأي نوع من سلامة الهيكل. وأود حقاً أن أعرف ما هي التكلفة الحقيقية للبنية في الإطار الصغير.

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

أنا أستخدم FEZ Domino كجهاز. من الممكن تماما هذا هو جهاز محدد. أيضا ، مايكرو الإطار 4.1.

تحرير - المزيد من الاختبارات وإجابات التعليق

جريت مجموعة كاملة من الاختبارات (في المحاكي هذه المرة ، وليس على أجهزة حقيقية ، ولكن الأرقام الخاصة بـ QueuedEvent هي نفسها ، لذلك أفترض أن QueuedEvent ستكون متطابقة لاختبارات أخرى).

BitBucket مستودع مع رمز لتكرار.

لا تجذب VALUETYPE المتكاملة التالية أي نفقات عامة مثل VALUETYPE :

  • بايت (1 بايت)
  • Int32 (4 بايت)
  • Int16 (2 بايت)
  • Int64 (8 بايت)
  • مزدوج (8 ​​بايت)
  • TimeSpan (12 بايت - غريب ، كعضو داخلي هو Int64)
  • DateTime (12 بايت - غريب)

ومع ذلك ، Guid يفعل: كل باستخدام 36 بايت.

يقوم العضو الثابت الفارغ بتخصيص VALUETYPE ، باستخدام 72 بايت (12 بايت أقل من نفس البنية في صفيف).

تخصيص الصفيف كعضو static لا يغير أي شيء.

التشغيل في أوضاع Debug أو Release يجعل لا فرق. لا أعرف كيفية الحصول على معلومات تصحيح GC دون وجود مصحح مرفقة مع ذلك. ولكن يتم تفسير Micro Framework ، لذلك لا أعرف ما تأثير أي مصحح غير مرفق على أي حال.

لا يدعم Micro Framework التعليمات البرمجية unsafe . كما أنه لا يدعم StructLayout Explicit (جيد ، من الناحية الفنية ، لكنه لا يوجد سمة FieldOffset ). StructLayout Auto StructLayout لا فرق.

في ما يلي بعض البنيات الأخرى وتخصيص الذاكرة المقاسة:

// 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 بايت في 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;
}

تحرير بسيط

بعد نشر إجابتي الخاصة ، أدركت أنه كان هناك دائمًا 12 بايت في SZARRAY لكل عنصر. لذلك اختبرت object[] . تستهلك أنواع المراجع 12 بايت لكل منها في Micro Framework.

بنية public struct Empty { } يستهلك 24 بايت لكل منهما.


استنادًا إلى اختباراتي ، أعتقد أن ValueTypes في Micro Framework ليست أنواعًا ذات قيمة حقيقية كما اعتدنا على سطح المكتب CLR. على أقل تقدير ، يتم تعبئتها. وقد يكون هناك مستوى آخر من التفاعل غير المباشر أيضًا. يتم تكبد هذه التكاليف في (ذاكرة كبيرة جداً لمنصة مضمّنة) في الذاكرة.

سأكون تحويل إلى int[] في بلدي FixedSizedQueue .

في الواقع ، انتهى بي الأمر باستخدام UInt32[] بعض طرق الإضافة UInt32[] حول تقريع البتة.

أنا مطعون قليلاً في التعليمات البرمجية المصدر ، ولكن لا يمكن العثور على أي شيء مفيد (وأنا حقا لا أعرف ما الذي تبحث عنه أما).





.net-micro-framework