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.