Ce chapitre présente certains principes de codage et de conception non abordés dans d'autres parties du livre. Contient certains scénarios d'application .NET, certains ne causeront pas beaucoup de dommages et d'autres causeront des problèmes évidents. Le reste aura des effets différents selon la façon dont vous l’utilisez. Si vous voulez résumer les principes présentés dans ce chapitre, c'est :
Une optimisation excessive affectera l'abstraction du code
Cela signifie que lorsque vous voulez plus haut To Pour optimiser les performances, vous devez comprendre les détails d'implémentation de chaque niveau de code. Il y aura de nombreuses introductions connexes dans ce chapitre.
Les instances de classes sont allouées sur le tas et accessibles via des références de pointeur. Passer ces objets est bon marché car il s'agit simplement d'une copie du pointeur (4 ou 8 directement). Cependant, les objets ont également une surcharge fixe : 8 ou 16 octets (systèmes 32 ou 64 bits). Cette surcharge inclut des pointeurs vers des tables de méthodes et des champs de synchronisation utilisés à d’autres fins. Cependant, si vous examinez la mémoire occupée par un objet vide via un outil de débogage, vous constaterez qu'elle est plus grande de 13 ou 24 octets (systèmes 32 bits ou 64 bits). Cela est dû au mécanisme d'alignement de la mémoire de .NET.
La structure n'a pas la surcharge ci-dessus et son utilisation de la mémoire est la combinaison des tailles de champ. Si la structure est une variable locale déclarée dans une méthode (fonction), elle alloue le contrôle sur la pile. Si une structure est déclarée comme faisant partie d'une classe, la mémoire utilisée par la structure fait partie de la disposition mémoire de la classe (elle est donc allouée sur le tas). Mais lorsque vous transmettez la structure à une méthode (fonction), elle copie les données d'octet. Parce qu'elle n'est pas sur le tas, la structure ne provoquera pas de garbage collection.
Il y a donc ici un compromis. Vous pouvez trouver diverses suggestions de tailles de structure, mais je ne vous donnerai pas de nombre exact ici. Dans la plupart des cas, vos structures devront rester de petite taille, surtout si elles doivent être transmises fréquemment. Vous voudrez vous assurer que la taille de la structure ne pose pas de problème trop important. La seule chose qui est sûre, c'est que vous devez l'analyser en fonction de vos propres scénarios d'application.
Dans certains cas, la différence d'efficacité est assez importante. La surcharge d'un objet ne semble pas être grande, mais vous pouvez voir la différence en comparant un tableau d'objets et un tableau de structures. Dans un système 32 bits, supposons qu'une structure de données contient 16 octets de données et que la longueur du tableau est de 100 W.
Espace occupé par l'utilisation d'un tableau d'objets
Surcharge de tableau de 8 octets +
(adresse de pointeur de 4 octets X1 000 000)+
((en-tête de 8 octets + données de 16 octets) 000 000)
= 28 Mo
Espace occupé par l'utilisation d'un tableau de structure
Surcharge du tableau de 8 octets +
(Données de 16 octets 🎜>Si vous utilisez un système 64 bits, le tableau d'objets utilise 40 Mo, tandis que le tableau de structure est toujours 16 Mo.
Vous pouvez voir que dans un tableau structuré, des données de même taille occupent moins de mémoire. À mesure que le nombre d’objets dans le tableau d’objets augmente, la pression sur le GC augmentera également.
Pour un tableau de structure, ce sont toutes des valeurs continues en mémoire. L'accès aux données dans le tableau de structure est très simple. Tant que vous trouvez la bonne position, vous pouvez obtenir la valeur correspondante. Cela signifie qu'il existe une énorme différence lors de l'itération sur de grands tableaux de données. Si la valeur est déjà dans le cache du processeur, y accéder est un ordre de grandeur plus rapide que l'accès à la RAM.
Si vous souhaitez accéder à un élément du tableau d'objets, vous devez d'abord obtenir la référence du pointeur de l'objet, puis y accéder dans le tas. Lors de l'itération du tableau d'objets, le pointeur de données sautera dans le tas, mettant fréquemment à jour le cache du processeur, gaspillant ainsi de nombreuses opportunités d'accéder aux données du cache du processeur.
Le problème est que dans la dernière ligne, vous essayez de modifier une certaine valeur de l'élément Point dans la liste. Cette opération n'est pas possible car des points. [0] Ce qui est renvoyé est une copie de la valeur d'origine. La bonne façon de modifier une valeur est
struct Point { public int x; public int y; } public static void Main() { List<Point> points = new List<Point>(); points.Add(new Point() {x = 1, y = 2}); points[0].x = 3; }
Cependant, vous pouvez adopter une stratégie de codage plus stricte : ne modifiez pas la structure. Une fois qu'une structure est créée, ses valeurs ne doivent jamais être modifiées. Cela élimine les problèmes de compilation ci-dessus et simplifie les règles d'utilisation des structures.
J'ai mentionné précédemment que les structures doivent rester petites pour éviter de passer beaucoup de temps à les copier, mais de temps en temps, certaines grandes structures seront utilisées. Par exemple, un objet de détails de processus métier final doit stocker un grand nombre d'horodatages :Point p = points[0]; p.x = 3; points[0] = p;
Afin de simplifier le code, nous pouvons diviser les données temporelles dans sa propre sous-structure, afin de pouvoir passez-le comme ceci Méthode pour accéder à l'objet Order :
class Order { public DateTime ReceivedTime { get; set; } public DateTime AcknowledgeTime { get; set; } public DateTime ProcessBeginTime { get; set; } public DateTime WarehouseReceiveTime { get; set; } public DateTime WarehouseRunnerReceiveTime { get; set; } public DateTime WarehouseRunnerCompletionTime { get; set; } public DateTime PackingBeginTime { get; set; } public DateTime PackingEndTime { get; set; } public DateTime LabelPrintTime { get; set; } public DateTime CarrierNotifyTime { get; set; } public DateTime ProcessEndTime { get; set; } public DateTime EmailSentToCustomerTime { get; set; } public DateTime CarrerPickupTime { get; set; } // lots of other data ... }
Nous pouvons mettre toutes les données dans notre propre classe :
class OrderTimes { public DateTime ReceivedTime { get; set; } public DateTime AcknowledgeTime { get; set; } public DateTime ProcessBeginTime { get; set; } public DateTime WarehouseReceiveTime { get; set; } public DateTime WarehouseRunnerReceiveTime { get; set; } public DateTime WarehouseRunnerCompletionTime { get; set; } public DateTime PackingBeginTime { get; set; } public DateTime PackingEndTime { get; set; } public DateTime LabelPrintTime { get; set; } public DateTime CarrierNotifyTime { get; set; } public DateTime ProcessEndTime { get; set; } public DateTime EmailSentToCustomerTime { get; set; } public DateTime CarrerPickupTime { get; set; } } class Order { public OrderTimes Times; }
但是,这样会为每个Order对象引入额外的12或者24字节的开销。如果你需要将OrderTimes对象作为一个整体传入各种方法函数里,这也许是有一定道理的,但为什么不把Order对象传入方法里呢?如果你同时有数千个Order对象,则可能会导致更多的垃圾回收,这是额外的对象增加的引用导致的。
相反,将OrderTime更改为结构体,通过Order上的属性(例如:Order.Times.ReceivedTime)访问OrderTImes结构体的各个属性,不会导致结构体的副本(.NET会对这个访问做优化)。这样OrderTimes结构体基本上成为Order类的内存布局的一部分,几乎和没有子结构体一样了,你拥有了更加漂亮的代码。
这种技术确实违反了不可变的结构体原理,但这里的技巧就是将OrderTimes结构的字段视为Order对象的字段。你不需要将OrderTimes结构体作为一个实体进行传递,它只是一个代码组织方式。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!