Maison > Article > développement back-end > Vous avez peut-être entendu parler de faire du GC sur la carte Golang ?
Golang, lors de la suppression de la paire clé-valeur, n'est pas réellement supprimée, mais marquée. Alors, comme il y a de plus en plus de paires clé-valeur, cela entraînera-t-il beaucoup de gaspillage de mémoire ? Tout d'abord, la réponse est oui, cela est très susceptible de provoquer un MOO, et il y a une discussion à ce sujet : github.com/golang/go/issues/20135. La signification générale est que dans une grande map
, l'opération delete
ne libère pas réellement la mémoire et peut provoquer un MOO mémoire.
L'approche générale est donc la suivante : map
中,delete
操作没有真正释放内存而可能导致内存 OOM。
所以一般的做法:就是 重建map。而 go-zero
中内置了 safemap
的容器组件。safemap
在一定程度上可以避免这种情况发生。
那首先我们看看 go
原生提供的 map
是怎么删除的?
1 package main 2 3 func main() { 4 m := make(map[int]string, 9) 5 m[1] = "hello" 6 m[2] = "world" 7 m[3] = "go" 8 9 v, ok := m[1] 10 _, _ = fn(v, ok) 11 12 delete(m, 1) 13 } 14 15 func fn(v string, ok bool) (string, bool) { 16 return v, ok 17 }
测试代码如上,我们可以通过 go tool compile -S -N -l testmap.go | grep "CALL"
:
0x0071 00113 (test/testmap.go:4) CALL runtime.makemap(SB) 0x0099 00153 (test/testmap.go:5) CALL runtime.mapassign_fast64(SB) 0x00ea 00234 (test/testmap.go:6) CALL runtime.mapassign_fast64(SB) 0x013b 00315 (test/testmap.go:7) CALL runtime.mapassign_fast64(SB) 0x0194 00404 (test/testmap.go:9) CALL runtime.mapaccess2_fast64(SB) 0x01f1 00497 (test/testmap.go:10) CALL "".fn(SB) 0x0214 00532 (test/testmap.go:12) CALL runtime.mapdelete_fast64(SB) 0x0230 00560 (test/testmap.go:7) CALL runtime.gcWriteBarrier(SB) 0x0241 00577 (test/testmap.go:6) CALL runtime.gcWriteBarrier(SB) 0x0252 00594 (test/testmap.go:5) CALL runtime.gcWriteBarrier(SB) 0x025c 00604 (test/testmap.go:3) CALL runtime.morestack_noctxt(SB)
执行第12行的 delete
,实际执行的是 runtime.mapdelete_fast64
。
这些函数的参数类型是具体的 int64
,mapdelete_fast64
跟原始的 delete
操作一样的,所以我们来看看 mapdelete
。
长图预警!!!
大致代码分析如上,具体代码就留给大家去阅读了。其实大致过程:
key
是否存在count--
所以你在大面积删除 key
,实际 map
存储的 key
是不会删除的,只是标记当前的key状态为 empty
。
其实出发点,和 mysql
的标记删除类似,防止后续会有相同的 key
插入,省去了扩缩容的操作。
但是这个对有些场景是不妥的,如果开发者在未来时间内都不会再插入相同的 key
,很可能会导致 OOM
。
所以针对以上情况,go-zero
开发了 safemap
。下面我们看看 safemap
是如何避免这个问题的?
直接从操作 safemap
中分析为什么要这么设计:
newmap
中map
是一个整体,所以 key
只能留一份所以为什么要设置两个 map
就很清楚了:
dirtyOld
作为存储主体,如果 delete
操作达到阈值,则会触发迁移。dirtyNew
作为暂存体,会在到达阈值时,存放部分 key/value
所以在迁移操作时,我们需要做的就是:将原先的 dirtyOld
清空,存储的 key/value 通过 for-range 重新存储到 dirtyNew
,然后将 dirtyNew
指向 dirtyOld
。
可能会有疑问:不是说
key/value
没有删除吗,只是标记了tophash=empty
其实在
for-range
过程中,会过滤掉tophash 的 key
这样就实现了不需要的 key 不会被加入到 dirtyNew
,进而不会影响 dirtyOld
reconstruire la carte
safemap
est intégré à go-zero
. safemap
peut empêcher cela dans une certaine mesure. Alors voyons d'abord comment supprimer la map
fournie nativement par go
?
Suppression de la carte native
rrreeego tool compile -S -N -l testmap.go | grep "CALL"
: rrreeeExecute line 12 delete
, l'exécution réelle est runtime.mapdelete_fast64
. Les types de paramètres de ces fonctions sont spécifiques à int64
, mapdelete_fast64
est le même que l'opération delete
d'origine, alors jetons un coup d'œil mapdelete
.
mapdeleteAttention longue photo ! ! !
🎜🎜🎜🎜 L'analyse générale du code est la même que ci-dessus, et le code spécifique est laissé à la lecture de tous. En fait, le processus général est le suivant : 🎜clé
à supprimer existecompte--
clé
dans une grande zone , la carte
réelle. La clé
stockée ne sera pas supprimée, mais l'état actuel de la clé sera marqué comme vide
. 🎜🎜En fait, le point de départ est similaire à la suppression de balise de mysql
, qui empêche la même clé
d'être insérée ultérieurement, éliminant ainsi le besoin d'opérations d'expansion et de contraction. 🎜🎜Mais cela est inapproprié pour certains scénarios. Si le développeur n'insère pas à nouveau la même clé
à l'avenir, cela provoquera probablement un MOO
. 🎜🎜Donc, en réponse à la situation ci-dessus, go-zero
a développé safemap
. Voyons comment safemap
évite ce problème ? 🎜🎜🎜🎜safemap🎜🎜 Analysez pourquoi il est conçu ainsi directement depuis l'opération safemap
: 🎜🎜🎜newmap
map
forment un tout, donc une seule copie de la key
peut être laisséemap
sont configurées : 🎜dirtyOld
comme sujet de stockage, si le delete
l’opération atteint le seuil, la migration sera déclenchée. dirtyNew
En tant que stockage temporaire, lorsque le seuil est atteint, une partie de la clé/valeur
dirtyOld
d'origine, stocker à nouveau la clé/valeur stockée dans dirtyNew
via for-range, puis. remplacez les points dirtyNew
par dirtyOld
🎜. 🎜🎜Vous avez peut-être des questions : Cela ne voulait-il pas dire que🎜De cette façon, les clés inutiles ne seront pas ajouté àkey/value
n'a pas été supprimé ? Il a simplement été marqué commetophash=empty
🎜🎜 ? 🎜en fait dansPendant le processus for-range
, les clés detophash seront filtrées🎜
dirtyNew
, ce qui n'affectera pas dirtyOld
. 🎜🎜🎜🎜🎜C'est en fait le concept de l'ancienne génération et de la nouvelle génération de collecte des déchets. 🎜🎜Pour plus de détails sur l'implémentation, vous pouvez consulter le code source ! 🎜🎜🎜🎜Adresse du projet🎜🎜github.com/tal-tech/go-zero🎜🎜Bienvenue pour utiliser go-zero et 🎜star🎜 soutenez-nous ! 🎜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!