In diesem Kapitel werden einige Codierungs- und Designprinzipien vorgestellt, die in anderen Teilen des Buches nicht behandelt werden. Enthält einige .NET-Anwendungsszenarien, von denen einige keinen großen Schaden anrichten und andere offensichtliche Probleme verursachen. Der Rest hat unterschiedliche Auswirkungen, je nachdem, wie Sie ihn verwenden. Wenn Sie die in diesem Kapitel vorgestellten Prinzipien zusammenfassen möchten, lautet dies:
Übermäßige Optimierung wirkt sich auf die Abstraktion des Codes aus
Dies bedeutet, dass, wenn Sie höhere Ziele erreichen möchten Um die Leistung zu optimieren, müssen Sie die Implementierungsdetails jeder Codeebene verstehen. In diesem Kapitel wird es viele verwandte Einführungen geben.
Instanzen von Klassen werden auf dem Heap zugewiesen und über Zeigerreferenzen aufgerufen. Die Übergabe dieser Objekte ist kostengünstig, da es sich nur um eine Kopie des Zeigers handelt (4 oder 8 direkt). Allerdings haben Objekte auch einen festen Overhead: 8 oder 16 Byte (32- oder 64-Bit-Systeme). Dieser Overhead umfasst Zeiger auf Methodentabellen und Synchronisierungsfelder, die für andere Zwecke verwendet werden. Wenn Sie sich jedoch mit einem Debugging-Tool den von einem leeren Objekt belegten Speicher ansehen, werden Sie feststellen, dass dieser 13 oder 24 Byte größer ist (32-Bit- oder 64-Bit-Systeme). Dies wird durch den Speicherausrichtungsmechanismus von .NET verursacht.
Die Struktur hat nicht den oben genannten Overhead und ihre Speichernutzung ist die Kombination der Feldgrößen. Wenn es sich bei der Struktur um eine lokale Variable handelt, die innerhalb einer Methode (Funktion) deklariert wird, weist sie die Kontrolle über den Stapel zu. Wenn eine Struktur als Teil einer Klasse deklariert wird, ist der von der Struktur verwendete Speicher Teil des Speicherlayouts der Klasse (also wird er auf dem Heap zugewiesen). Wenn Sie die Struktur jedoch an eine Methode (Funktion) übergeben, werden die Bytedaten kopiert. Da es sich nicht auf dem Heap befindet, verursacht die Struktur keine Speicherbereinigung.
Hier gibt es also einen Kompromiss. Sie können verschiedene Vorschläge für Strukturgrößen finden, ich werde Ihnen hier jedoch keine genaue Zahl nennen. In den meisten Fällen müssen Ihre Strukturen klein gehalten werden, insbesondere wenn sie häufig weitergegeben werden müssen. Sie sollten sicherstellen, dass die Größe der Struktur kein zu großes Problem darstellt. Sicher ist nur, dass Sie es anhand Ihrer eigenen Anwendungsszenarien analysieren müssen.
In manchen Fällen ist der Unterschied in der Effizienz recht groß. Der Overhead eines Objekts scheint nicht groß zu sein, aber Sie können den Unterschied erkennen, wenn Sie ein Array von Objekten und ein Array von Strukturen vergleichen. Gehen Sie bei einem 32-Bit-System davon aus, dass eine Datenstruktur 16 Datenbytes enthält und die Array-Länge 100 W beträgt.
Durch die Verwendung des Objektarrays belegter Speicherplatz
8-Byte-Array-Overhead +
(4-Byte-Zeigeradresse X1.000.000)+
((8-Byte-Header + 16-Byte-Daten) 000.000)
=28 MB
Speicherplatz, der durch die Verwendung des Strukturarrays belegt wird
8-Byte-Array-Overhead +
(16-Byte-Daten 🎜>Wenn Sie ein 64-Bit-System verwenden, verwendet das Objektarray 40 MB, während das Strukturarray dies tut immer noch 16 MB.
Sie können sehen, dass in einem Strukturarray Daten gleicher Größe weniger Speicher beanspruchen. Mit zunehmender Anzahl von Objekten im Objektarray nimmt auch der Druck auf den GC zu.
Bei einem Strukturarray handelt es sich alle um kontinuierliche Werte im Speicher. Der Zugriff auf die Daten im Strukturarray ist sehr einfach. Solange Sie die richtige Position finden, können Sie den entsprechenden Wert erhalten. Dies bedeutet, dass es einen großen Unterschied gibt, wenn über große Datenfelder iteriert wird. Befindet sich der Wert bereits im Cache der CPU, ist seine Zugriffsgeschwindigkeit um eine Größenordnung schneller als der Zugriff auf den RAM.
Wenn Sie auf ein Element im Objektarray zugreifen möchten, müssen Sie zuerst die Zeigerreferenz des Objekts abrufen und dann im Heap darauf zugreifen. Beim Iterieren des Objektarrays springt der Datenzeiger in den Heap, wodurch der CPU-Cache häufig aktualisiert wird und so viele Möglichkeiten für den Zugriff auf die CPU-Cache-Daten verschwendet werden.
Das Problem besteht darin, dass Sie in der letzten Zeile versuchen, einen bestimmten Wert des Point-Elements in der Liste zu ändern. Dieser Vorgang ist aufgrund von Punkten nicht möglich [0] Was zurückgegeben wird, ist eine Kopie des Originalwerts. Der richtige Weg, einen Wert zu ändern, ist
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; }
Sie können jedoch eine strengere Codierungsstrategie anwenden: Ändern Sie die Struktur nicht. Sobald eine Struktur erstellt wurde, sollten ihre Werte niemals geändert werden. Dadurch werden die oben genannten Kompilierungsprobleme beseitigt und die Regeln für die Verwendung von Strukturen vereinfacht.
Ich habe bereits erwähnt, dass Strukturen klein gehalten werden sollten, um nicht viel Zeit mit dem Kopieren zu verbringen, aber gelegentlich werden einige große Strukturen verwendet. Beispielsweise muss ein Objekt mit endgültigen Geschäftsprozessdetails eine große Anzahl von Zeitstempeln speichern:Point p = points[0]; p.x = 3; points[0] = p;
Um den Code zu vereinfachen, können wir die Zeitdaten in eine eigene Unterstruktur unterteilen, damit wir dies tun können Übergeben Sie es wie diese Methode, um auf das Order-Objekt zuzugreifen:
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 ... }
Wir können alle Daten in unsere eigene Klasse einfügen:
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结构体作为一个实体进行传递,它只是一个代码组织方式。
Das obige ist der detaillierte Inhalt vonFassen Sie einige Beispiele für Codierungs- und Designprinzipien zusammen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!