Heim  >  Artikel  >  Backend-Entwicklung  >  Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?

Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?

藏色散人
藏色散人nach vorne
2021-08-13 14:26:462487Durchsuche

Golang wird beim Löschen des Schlüssel-Wert-Paares nicht tatsächlich gelöscht, sondern markiert. Wird es also zu einer großen Speicherverschwendung kommen, wenn es immer mehr Schlüssel-Wert-Paare gibt? Zuallererst lautet die Antwort: Ja, es ist sehr wahrscheinlich, dass es OOM verursacht, und es gibt eine Diskussion darüber: github.com/golang/go/issues/20135. Die allgemeine Bedeutung ist, dass in einer großen map der delete-Vorgang den Speicher nicht tatsächlich freigibt und zu Speicher-OOM führen kann.

Der allgemeine Ansatz lautet also: map 中,delete 操作没有真正释放内存而可能导致内存 OOM。

所以一般的做法:就是 重建map。而 go-zero 中内置了 safemap 的容器组件。safemap 在一定程度上可以避免这种情况发生。

那首先我们看看 go 原生提供的 map 是怎么删除的?

原生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

这些函数的参数类型是具体的 int64mapdelete_fast64 跟原始的 delete 操作一样的,所以我们来看看 mapdelete

mapdelete

长图预警!!!

Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?

大致代码分析如上,具体代码就留给大家去阅读了。其实大致过程:

  1. 写保护,防止并发写
  2. 查询要删除的 key 是否存在
  3. 存在则对其标志做删除标记
  4. count--

所以你在大面积删除 key ,实际 map 存储的 key 是不会删除的,只是标记当前的key状态为 empty

其实出发点,和 mysql 的标记删除类似,防止后续会有相同的 key 插入,省去了扩缩容的操作。

但是这个对有些场景是不妥的,如果开发者在未来时间内都不会再插入相同的 key ,很可能会导致 OOM

所以针对以上情况,go-zero 开发了 safemap 。下面我们看看 safemap 是如何避免这个问题的?

safemap

直接从操作 safemap 中分析为什么要这么设计:

Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?

  1. 预设一个 删除阈值,如果触发会放到一个新预设好的 newmap
  2. 两个 map 是一个整体,所以 key 只能留一份

所以为什么要设置两个 map 就很清楚了:

  1. dirtyOld 作为存储主体,如果 delete 操作达到阈值,则会触发迁移。
  2. dirtyNew 作为暂存体,会在到达阈值时,存放部分 key/value

所以在迁移操作时,我们需要做的就是:将原先的 dirtyOld 清空,存储的 key/value 通过 for-range 重新存储到 dirtyNew,然后将 dirtyNew 指向 dirtyOld

可能会有疑问:不是说 key/value 没有删除吗,只是标记了 tophash=empty

其实在 for-range 过程中,会过滤掉 tophash 的 key

这样就实现了不需要的 key 不会被加入到 dirtyNew,进而不会影响 dirtyOldErstellen Sie die Karte neu

. Die Containerkomponente von safemap ist in go-zero integriert. safemap kann dies bis zu einem gewissen Grad vermeiden.

Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?Dann sehen wir uns zunächst an, wie die von go nativ bereitgestellte map gelöscht wird?

Native Map-Löschung

rrreee

Der Testcode ist wie oben, wir können go tool compile -S -N -l testmap.go | übergeben: <span class="header-link octicon octicon-link">rrreee</span>Zeile ausführen 12 <code>delete, die tatsächliche Ausführung ist runtime.mapdelete_fast64.

Die Parametertypen dieser Funktionen sind spezifisch int64, mapdelete_fast64 ist derselbe wie der ursprüngliche delete-Vorgang, also werfen wir einen Blick auf Mapdelete.

mapdeleteLange Bildwarnung! ! !

🎜🎜Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen? 🎜🎜 Die allgemeine Codeanalyse ist wie oben beschrieben, und der spezifische Code kann von jedem gelesen werden. Tatsächlich ist der allgemeine Prozess: 🎜
  1. Schreibschutz, um gleichzeitiges Schreiben zu verhindern
  2. Abfragen, ob der zu löschende Schlüssel vorhanden ist
  3. Wenn es existiert, markieren Sie es. Machen Sie Löschmarkierungen
  4. count--
🎜So löschen Sie key in einem großen Bereich , die tatsächliche map Der gespeicherte Schlüssel wird nicht gelöscht, aber der aktuelle Schlüsselstatus wird als leer markiert. 🎜🎜Tatsächlich ähnelt der Ausgangspunkt dem Tag-Löschen von mysql, wodurch verhindert wird, dass derselbe key später eingefügt wird, wodurch Erweiterungs- und Kontraktionsvorgänge überflüssig werden. 🎜🎜Aber dies ist für einige Szenarien ungeeignet. Wenn der Entwickler in Zukunft nicht mehr denselben Schlüssel einfügt, führt dies wahrscheinlich zu OOM. 🎜🎜Als Reaktion auf die obige Situation entwickelte go-zero safemap. Mal sehen, wie safemap dieses Problem vermeidet? 🎜🎜🎜🎜safemap🎜🎜 Analysieren Sie, warum es so gestaltet ist, direkt aus der Operation safemap: 🎜🎜Sie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?🎜
  1. Legen Sie einen 🎜Löschschwellenwert🎜 fest, der bei Auslösung in einem neuen Preset platziert wirdnewmap
  2. Die beiden map sind ein Ganzes, sodass nur eine Kopie von key übrig bleiben kann
🎜Es ist also klar, warum zwei map eingerichtet werden: 🎜
  1. dirtyOld als Speichergegenstand, wenn der delete Wenn der Vorgang den Schwellenwert erreicht, wird die Migration ausgelöst.
  2. dirtyNew Als temporärer Speicher wird bei Erreichen des Schwellenwerts ein Teil des Schlüssels/Wertes
gespeichert . Also während der Migration Während des Betriebs müssen wir nur Folgendes tun: 🎜Löschen Sie den ursprünglichen dirtyOld, speichern Sie den gespeicherten Schlüssel/Wert erneut in dirtyNew über for-range und dann change dirtyNew zeigt auf dirtyOld🎜. 🎜
🎜Sie haben möglicherweise Fragen: Bedeutete das nicht, dass key/value nicht gelöscht wurde? Es wurde nur als tophash=empty markiert 🎜🎜 🎜eigentlich werden in Während des for-range-Prozesses die Schlüssel von tophash herausgefiltert🎜
🎜Auf diese Weise werden unnötige Schlüssel nicht entfernt zu dirtyNew hinzugefügt, was sich nicht auf dirtyOld auswirkt. 🎜🎜🎜🎜🎜Das ist eigentlich das Konzept der alten Generation und der neuen Generation der Müllabfuhr. 🎜🎜Weitere Implementierungsdetails finden Sie im Quellcode! 🎜🎜🎜🎜Projektadresse🎜🎜github.com/tal-tech/go-zero🎜🎜Willkommen bei go-zero und 🎜star🎜 unterstützt uns! 🎜

Das obige ist der detaillierte Inhalt vonSie haben vielleicht schon davon gehört, GC auf der Golang-Karte durchzuführen?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen