Der PHP-Editor Yuzi stellt Ihnen eine Technik zur Optimierung der Speichernutzung vor – das Freigeben von Speicher aus großen Objekten. Während des Entwicklungsprozesses erstellen wir häufig einige große Objekte, z. B. große Arrays oder große Datenbankabfrageergebnisse, und diese Objekte beanspruchen viele Speicherressourcen. Wenn wir mit der Verwendung dieser Objekte fertig sind, ist es eine gute Programmiergewohnheit, den Speicher rechtzeitig freizugeben. In diesem Artikel erfahren Sie, wie Sie Speicher für große Objekte freigeben, um die Leistung und Effizienz von Anwendungen zu verbessern.
Ich bin auf etwas gestoßen, das ich nicht verstehe. Ich hoffe, Sie können alle helfen!
Ressource:
Ich habe in einigen Artikeln gelesen, dass wir die Arbeit des GC vereinfachen könnten, indem wir große Slices und Maps (ich denke, das gilt für alle Referenztypen) auf nil
setzen, nachdem wir sie nicht mehr benötigen. Hier ist eines der Beispiele, die ich gelesen habe:
func ProcessResponse(resp *http.Response) error { data, err := ioutil.ReadAll(resp.Body) if err != nil { return err } // Process data here data = nil // Release memory return nil }
Soweit ich weiß, wird der Speicher gelöscht, wenn die Funktion processresponse
完成时,data
变量将超出范围,基本上将不再存在。然后,gc 将验证是否没有对 []byte
切片(data
auf einen Verweis auf das Slice verweist.
Setzen Sie data
auf data
设置为 nil
Wie kann die Speicherbereinigung verbessert werden?
Danke!
data = nil
不会改变 gc 方面的任何内容。 go 编译器将应用优化,并且 golang 的垃圾收集器在不同的阶段工作。用最简单的术语(有许多遗漏和过度简化):设置 data = nil
vor der Rückkehr und das Entfernen aller Verweise auf den zugrunde liegenden Slice löst keine atomare Freigabe von Speicher aus, auf den nicht mehr verwiesen wird. Sobald ein Slice nicht mehr referenziert wird, wird er als solcher markiert und der zugehörige Speicher wird erst beim nächsten Scan freigegeben.
Die Garbage Collection ist ein schwieriges Problem, vor allem, weil es nicht die Art von Problem ist, für die es eine optimale Lösung gibt, die für alle Anwendungsfälle die besten Ergebnisse liefert. Die Go-Laufzeit hat sich im Laufe der Jahre stark weiterentwickelt und die wichtige Arbeit wird am Laufzeit-Garbage Collector durchgeführt. Das Ergebnis ist, dass in seltenen Fällen ein einfaches somevar = nil
auch nur einen kleinen, geschweige denn einen spürbaren Unterschied macht.
Wenn Sie nach einfachen Faustregel-Tipps suchen, die sich auf den mit der Speicherbereinigung (oder der Laufzeitspeicherverwaltung im Allgemeinen) verbundenen Laufzeitaufwand auswirken können, weiß ich, dass dieser Satz den in Ihrem Artikel vage abzudecken scheint Fragen:
Es wird vorgeschlagen, dass wir die Arbeit von gc vereinfachen können, indem wir große Slices und Mappings einrichten
Dies kann bei der Codeanalyse zu erheblichen Ergebnissen führen. Angenommen, Sie lesen eine große Datenmenge, die verarbeitet werden muss, oder Sie müssen eine andere Art von Batch-Vorgang ausführen und Slices zurückgeben, ist es nicht ungewöhnlich, dass Leute so etwas schreiben:
func processstuff(input []sometypes) []resulttypes { data := []resulttypes{} for _, in := range input { data = append(data, processt(in)) } return data }
Einfache Optimierung durch Ändern des Codes in:
func processstuff(input []sometypes) []resulttypes { data := make([]resulttypes, 0, len(input)) // set cap for _, in := range input { data = append(data, processt(in)) } return data }
Was bei der ersten Implementierung passiert, ist, dass Sie bei Verwendung von len
和 cap
为 0 创建一个切片。第一次调用 append
die aktuelle Kapazität des Slice überschreiten, was dazu führt, dass die Laufzeit Speicher zuweist. Wie hier erklärt, ist die Berechnung der neuen Kapazität ziemlich einfach, der Speicher wird zugewiesen und die Daten werden kopiert:
t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s)
Im Wesentlichen jedes Mal, wenn das anzuhängende Slice voll ist (d. h. len
== cap
)调用 append
时,您将分配一个可容纳: (len + 1) * 2
元素的新切片。知道在第一个示例中 data
以 len
和 cap
== 0 zu Beginn), schauen wir uns an, was das bedeutet:
1st iteration: append creates slice with cap (0+1) *2, data is now len 1, cap 2 2nd iteration: append adds to data, now has len 2, cap 2 3rd iteration: append allocates a new slice with cap (2 + 1) *2, copies the 2 elements from data to this slice and adds the third, data is now reassigned to a slice with len 3, cap 6 4th-6th iterations: data grows to len 6, cap 6 7th iteration: same as 3rd iteration, although cap is (6 + 1) * 2, everything is copied over, data is reassigned a slice with len 7, cap 14
Wenn die Datenstrukturen im Slice groß sind (d. h. viele verschachtelte Strukturen, viele Indirektionen usw.), kann dieses häufige Neuzuordnen und Kopieren ziemlich teuer werden. Wenn Ihr Code viele dieser Schleifen enthält, wird er in pprof angezeigt (Sie werden feststellen, dass Aufrufe viel Zeit in Anspruch nehmen gcmalloc
). Wenn Sie außerdem 15 Eingabewerte verarbeiten würden, würde Ihr Datenausschnitt am Ende so aussehen:
dataslice { len: 15 cap: 30 data underlying_array[30] }
Das bedeutet, dass Sie Speicher für 30 Werte zuweisen, wenn Sie nur 15 benötigen, und dass Sie diesen Speicher in 4 schrittweise größere Blöcke zuweisen und die Daten bei jeder Neuzuweisung kopieren.
Im Gegensatz dazu weist die zweite Implementierung vor der Schleife einen Datenausschnitt wie diesen zu:
data { len: 0 cap: 15 data underlying_array[15] }
Es wird einmal zugewiesen, sodass keine Neuzuweisung und kein Kopieren erforderlich sind und der zurückgegebene Slice die Hälfte des Speicherplatzes belegt. In diesem Sinne weisen wir zu Beginn zunächst größere Speicherblöcke zu, um die Anzahl der später erforderlichen inkrementellen Zuordnungs- und Kopieraufrufe zu reduzieren, was insgesamt die Laufzeitkosten senkt.
这是一个公平的问题。这个例子并不总是适用。在这种情况下,我们知道需要多少个元素,并且可以相应地分配内存。有时,世界并不是这样运作的。如果您不知道最终需要多少数据,那么您可以:
不,将一个简单的切片变量设置为 nil 在 99% 的情况下不会产生太大影响。创建和附加到地图/切片时,更可能产生影响的是通过使用 make()
+ 指定合理的 cap
值来减少无关分配。其他可以产生影响的事情是使用指针类型/接收器,尽管这是一个需要深入研究的更复杂的主题。现在,我只想说,我一直在开发一个代码库,该代码库必须对远远超出典型 uint64
范围的数字进行操作,不幸的是,我们必须能够以更精确的方式使用小数比 float64
将允许。我们通过使用像 holiman/uint256 这样的东西解决了 uint64
问题,它使用指针接收器,并解决shopspring/decimal 的十进制问题,它使用值接收器并复制所有内容。在花费大量时间优化代码之后,我们已经达到了使用小数时不断复制值的性能影响已成为问题的地步。看看这些包如何实现加法等简单操作,并尝试找出哪个操作成本更高:
// original a, b := 1, 2 a += b // uint256 version a, b := uint256.NewUint(1), uint256.NewUint(2) a.Add(a, b) // decimal version a, b := decimal.NewFromInt(1), decimal.NewFromInt(2) a = a.Add(b)
这些只是我在最近的工作中花时间优化的几件事,但从中得到的最重要的一点是:
当您处理更复杂的问题/代码时,您需要花费大量精力来研究切片或映射的分配周期,因为潜在的瓶颈和优化需要付出很大的努力。您可以而且可以说应该采取措施避免过于浪费(例如,如果您知道所述切片的最终长度是多少,则设置切片上限),但您不应该浪费太多时间手工制作每一行,直到该代码的内存占用尽可能小。成本将是:代码更脆弱/更难以维护和阅读,整体性能可能会恶化(说真的,你可以相信 go 运行时会做得很好),大量的血、汗和泪水,以及急剧下降在生产力方面。
Das obige ist der detaillierte Inhalt vonGeben Sie Speicher von großen Objekten frei. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!