L'éditeur PHP Youzi vous présente une technique pour optimiser l'utilisation de la mémoire : libérer la mémoire des objets volumineux. Au cours du processus de développement, nous créons souvent des objets volumineux, tels que de grands tableaux ou des résultats de requêtes de base de données volumineux, et ces objets occupent beaucoup de ressources mémoire. Lorsque nous avons fini d'utiliser ces objets, c'est une bonne habitude de programmation de libérer la mémoire à temps. Cet article vous montrera comment libérer de la mémoire des objets volumineux pour améliorer les performances et l'efficacité des applications.
J'ai rencontré quelque chose que je ne comprends pas. J'espère que vous pourrez tous aider !
Ressources :
J'ai lu dans quelques articles que nous pourrions simplifier le travail de gc en définissant de grandes tranches et cartes (je suppose que cela s'applique à tous les types de référence) sur nil
une fois que nous n'en aurons plus besoin. Voici l'un des exemples que j'ai lus :
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 }
Si je comprends bien, lorsque la fonction processresponse
完成时,data
变量将超出范围,基本上将不再存在。然后,gc 将验证是否没有对 []byte
切片(data
pointe vers une référence à la tranche), la mémoire sera effacée.
Définissez data
sur data
设置为 nil
Comment améliorer le garbage collection ?
Merci !
data = nil
不会改变 gc 方面的任何内容。 go 编译器将应用优化,并且 golang 的垃圾收集器在不同的阶段工作。用最简单的术语(有许多遗漏和过度简化):设置 data = nil
avant de revenir et supprimer toutes les références à la tranche sous-jacente ne déclenche pas une libération de mémoire de type atomique qui n'est plus référencée. Dès qu'une tranche n'est plus référencée, elle est marquée comme telle et la mémoire associée n'est libérée qu'au prochain scan.
Le garbage collection est un problème difficile, en grande partie parce que ce n'est pas le genre de problème pour lequel une solution optimale produit les meilleurs résultats pour tous les cas d'utilisation. Le runtime go a beaucoup évolué au fil des années, et le travail important est effectué sur le garbage collector du runtime. Le résultat est que, dans de rares cas, un simple somevar = nil
fera même une petite différence, et encore moins une différence notable.
Si vous recherchez quelques conseils simples de type règle empirique qui pourraient avoir un impact sur la surcharge d'exécution associée au garbage collection (ou à la gestion de la mémoire d'exécution en général), je sais que cette phrase semble couvrir vaguement celle de votre questions :
Il est suggéré de simplifier le travail de gc en configurant de grandes tranches et des mappages
Cela peut donner des résultats significatifs lors de l'analyse du code. En supposant que vous lisez une grande quantité de données qui doivent être traitées, ou que vous deviez effectuer un autre type d'opération par lots et renvoyer des tranches, il n'est pas rare que les gens écrivent quelque chose comme ceci :
func processstuff(input []sometypes) []resulttypes { data := []resulttypes{} for _, in := range input { data = append(data, processt(in)) } return data }
Facile à optimiser en changeant le code en :
func processstuff(input []sometypes) []resulttypes { data := make([]resulttypes, 0, len(input)) // set cap for _, in := range input { data = append(data, processt(in)) } return data }
Ce qui se passe lors de la première implémentation, c'est que lorsque vous utilisez len
和 cap
为 0 创建一个切片。第一次调用 append
, vous dépassez la capacité actuelle de la tranche, ce qui oblige le runtime à allouer de la mémoire. Comme expliqué ici, le calcul de la nouvelle capacité est assez simple, la mémoire est allouée et les données sont copiées :
t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s)
Essentiellement, chaque fois que la tranche à ajouter est pleine (c'est-à-dire len
== cap
)调用 append
时,您将分配一个可容纳: (len + 1) * 2
元素的新切片。知道在第一个示例中 data
以 len
和 cap
== 0 pour commencer, voyons ce que cela signifie :
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
Si les structures de données dans la tranche sont volumineuses (c'est-à-dire de nombreuses structures imbriquées, beaucoup d'indirection, etc.), alors cette réallocation et cette copie fréquentes peuvent devenir assez coûteuses. Si votre code contient beaucoup de ces boucles, il commencera à apparaître dans pprof (vous commencerez à voir des appels prendre beaucoup de temps gcmalloc
). De plus, si vous traitiez 15 valeurs d'entrée, votre tranche de données ressemblerait à ceci :
dataslice { len: 15 cap: 30 data underlying_array[30] }
Cela signifie que vous allouerez de la mémoire pour 30 valeurs alors que vous n'en avez besoin que de 15, et que vous allouerez cette mémoire en 4 morceaux progressivement plus grands, en copiant les données à chaque réallocation.
En revanche, la deuxième implémentation allouera une tranche de données comme celle-ci avant la boucle :
data { len: 0 cap: 15 data underlying_array[15] }
Il est alloué une fois, donc aucune réallocation ni copie n'est requise, et la tranche renvoyée occupera la moitié de l'espace mémoire. En ce sens, nous allouons d’abord des blocs de mémoire plus grands au début pour réduire le nombre d’appels d’allocation et de copie incrémentielle requis plus tard, ce qui réduit globalement le coût d’exécution.
这是一个公平的问题。这个例子并不总是适用。在这种情况下,我们知道需要多少个元素,并且可以相应地分配内存。有时,世界并不是这样运作的。如果您不知道最终需要多少数据,那么您可以:
不,将一个简单的切片变量设置为 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 运行时会做得很好),大量的血、汗和泪水,以及急剧下降在生产力方面。
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!