59 votes

Pourquoi mon tableau de structs prend-il autant de mémoire ?

Question : Comment le Micro Framework alloue-t-il la mémoire pour un tableau de structs ?

Dépôt BitBucket avec un code à reproduire.

Contexte et détails

Je crée une file d'attente en utilisant un tableau de taille fixe pour insérer des délais dans le traitement des frappes d'un clavier USB. J'utilise un struct pour représenter les événements de montée et de descente des touches et le délai.

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

J'aurais pensé que mon QueuedEvent occuperait 4 dans la mémoire, mais, en regardant la sortie de débogage du ramasseur d'ordures (en particulier la fenêtre VALUETYPE y SZARRAY ), il occupe en fait 84 octets chacun ! Cela me semble excessif ! (Et il semble que ce soit vraiment 84 octets chacun, car j'obtiens un message d'erreur OutOfMemoryException si j'en alloue 512. J'ai ~20kB de RAM disponible, donc je devrais être capable d'en allouer 512 facilement).

Question (encore) : Comment le Micro Framework parvient-il à allouer 84 octets à une structure qui pourrait tenir dans 4 ?

Figures du collecteur d'ordures

Voici un tableau de tableaux de tailles différentes de QueuedEvent (après avoir soustrait les montants lorsque j'attribue 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    |
+--------+-----------+-----------+---------+------------+-------+

Sur la base de la SZARRAY les chiffres, je suppose que mon QueuedEvent sont alignés sur les limites des Int32, ce qui occupe donc 12 octets. Mais je n'ai aucune idée d'où viennent les 72 octets supplémentaires.

Edita: J'obtiens ces numéros en appelant Debug.GC(true) et observer le vidage que j'obtiens dans la sortie de mon débogueur. Je n'ai pas trouvé de référence qui identifie exactement la signification de chacun de ces chiffres.

Je réalise que je pourrais simplement allouer un int[] mais cela signifie que je perds l'encapsulation et la sécurité de type de la structure. Et j'aimerais vraiment savoir quel est le coût réel d'une structure dans le micro-cadre.


Mon TinyTimeSpan ressemble beaucoup à une TimeSpan sauf qu'il utilise un Int16 pour représenter un nombre de millisecondes plutôt qu'un Int64 représentant des ticks de 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; } }
}

J'utilise un Domino FEZ comme du matériel. Il est tout à fait possible que ce soit spécifique au matériel. Aussi, Micro Framework 4.1.

Edit - Plus de tests et réponses aux commentaires

J'ai effectué un grand nombre de tests supplémentaires (dans l'émulateur cette fois, et non sur du matériel réel, mais les chiffres pour les QueuedEvent sont les mêmes, je suppose donc que mon matériel serait identique pour les autres tests).

Dépôt BitBucket avec un code à reproduire.

Les types intégraux et les structs suivants n'entraînent pas de frais généraux en tant que VALUETYPE :

  • Octet (1 octet)
  • Int32 (4 octets)
  • Int16 (2 octets)
  • Int64 (8 octets)
  • Double (8 octets)
  • TimeSpan (12 octets - étrange, car son membre interne est un Int64)
  • DateTime (12 octets - étrange)

Cependant, Guid fait : chacun utilise 36 octets.

Le membre statique vide alloue bien VALUETYPE Il s'agit d'une structure qui utilise 72 octets (12 octets de moins que la même structure dans un tableau).

L'allocation du tableau en tant que static ne change rien.

L'exécution en mode Debug ou Release ne fait aucune différence. Je ne sais pas comment obtenir les informations de débogage GC sans débogueur. Mais Micro Framework est interprété, donc je ne sais pas quel effet aurait un débogueur non attaché de toute façon.

Le Micro Framework ne prend pas en charge unsafe code. Il ne prend pas non plus en charge StructLayout Explicit (enfin, techniquement, c'est le cas, mais il n'y a pas de FieldOffset attribut) . StructLayout Auto y Sequential ne font aucune différence.

Voici quelques structs supplémentaires et leur allocation de mémoire mesurée :

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

Il semble que chaque fois que j'utilise une structure personnalisée, je subis une sorte de surcharge. Et peu importe ce que j'inclus dans la structure, il faut toujours 12 octets dans le fichier SZARRAY . J'ai donc essayé ceci :

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

Modification mineure

Après avoir posté ma propre réponse, j'ai réalisé qu'il y avait toujours une surcharge de 12 octets dans le fichier SZARRAY par article. J'ai donc testé un object[] . Les types de référence consomment 12 octets chacun dans le Micro Framework.

Une structure vide public struct Empty { } consomme 24 octets chacun.

13voto

ligos Points 3149

D'après mes tests, je suppose que ValueTypes dans le Micro Framework ne sont pas de vrais types de valeurs comme ceux auxquels nous sommes habitués dans le CLR de bureau. Au minimum, ils sont mis en boîte. Et il peut y avoir un autre niveau d'indirection impliqué aussi. Ces coûts se traduisent par une surcharge mémoire (assez importante pour une plate-forme embarquée).

Je vais me convertir en un int[] dans mon FixedSizedQueue .

En fait, j'ai fini par utiliser UInt32[] et ajouté quelques méthodes d'extension pour envelopper le bit bashing.

J'ai fouillé un peu dans le code source mais je n'ai rien trouvé d'utile (et je ne sais pas vraiment quoi chercher non plus).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X