Inhaltsverzeichnis
mheap" >mheap
# Die Regeln für den Speicherzuordnungsort" ># Die Regeln für den Speicherzuordnungsort
根据变量类型是否确定" > 根据变量类型是否确定
根据变量的占用大小" > 根据变量的占用大小
根据变量长度是否确定" > 根据变量长度是否确定
Heim Backend-Entwicklung Golang Ein Artikel erklärt die Speicherzuweisung in Go

Ein Artikel erklärt die Speicherzuweisung in Go

Jul 25, 2023 pm 01:57 PM
go


Heute werde ich einige allgemeine Wissenspunkte über die Speicherverwaltung in Go mit Ihnen teilen.

# 1. Drei Hauptkomponenten der Speicherzuweisung

Go Der Prozess der Speicherzuweisung wird hauptsächlich durch drei Hauptkomponenten verwaltet:

mheap

Go Wenn das Programm startet, beantragt es zunächst einen großen Speicherblock vom Betriebssystem und lässt ihn global durch die mheap-Struktur verwalten.

Wie geht man konkret damit um? mheap unterteilt diesen großen Speicherblock in kleine Speicherblöcke mit unterschiedlichen Spezifikationen, die wir als mspan bezeichnen. Je nach Spezifikation gibt es etwa 70 Arten von mspan. Man kann sagen, dass die Aufteilung sehr fein ist und den Anforderungen von entspricht verschiedene Objektspeicher.

Diese Mspan-Spezifikationen verschiedener Größen, gemischt, müssen also schwierig zu verwalten sein, oder?

Es gibt also die Komponente der nächsten Ebene mcentral

mcentral

Durch das Starten eines Go-Programms werden viele mcentrals initialisiert, und jedes mcentral ist nur für die Verwaltung von Mspans einer bestimmten Spezifikation verantwortlich.

Entspricht mcentral, das eine verfeinerte Verwaltung von mspan basierend auf mheap implementiert.

Aber mcentral ist im Go-Programm global sichtbar, sodass die Coroutine jedes Mal, wenn sie zu mcentral kommt, um Speicher zu beantragen, gesperrt werden muss.

Es ist zu erwarten, dass der Aufwand für häufiges Sperren und Freigeben sehr groß ist, wenn jede Coroutine zu mcentral kommt, um Speicher zu beantragen.

Daher ist ein sekundärer Proxy von mcentral erforderlich, um diesen Druck abzufedern

mcache

In einem Go-Programm ist jeder ThreadM wird an einen Prozessor gebundenP, es kann nur ein Multiverarbeitungslauf mit einer einzelnen Zeitgranularität ausgeführt werdengoroutine, everyP wird gebunden. Eins ist aufgerufen mcaches lokaler Cache. M会绑定给一个处理器P,在单一粒度的时间里只能做多处理运行一个goroutine,每个P都会绑定一个叫 mcache 的本地缓存。

当需要进行内存分配时,当前运行的goroutine会从mcache中查找可用的mspan。从本地mcache

Wenn eine Speicherzuweisung erforderlich ist, wird der aktuell ausgeführte goroutine beginnt mit Verfügbare <code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px in mcache 4px finden ;Randradius: 4px;Rand-rechts: 2px;Rand-links: 2px;Farbe: rgb(226, 36, 70);Hintergrund: rgb(248, 248, 248);">mspan. From localmcache erfordert keine Sperre bei der Speicherzuweisung. Diese Zuweisungsstrategie ist effizienter.

mspan-Lieferkette

Die Anzahl der Mspans im mcache reicht nicht immer aus. Wenn das Angebot die Nachfrage übersteigt, beantragt mcache erneut weitere mspans bei mcentral wird auch Mspan von seinem übergeordneten Mheap beantragen. Um es noch extremer auszudrücken: Was sollten wir tun, wenn der Mspan in Mheap die Speicheranforderung des Programms nicht erfüllen kann?

Dann geht es nicht anders, mheap lässt sich nur schamlos auf den großen Bruder des Betriebssystems anwenden.

Ein Artikel erklärt die Speicherzuweisung in Go

🎜

Der obige Bereitstellungsprozess gilt nur für Szenarien, in denen der Speicherblock weniger als 64 KB beträgt. Der Grund dafür ist, dass Go den lokalen Cache des Arbeitsthreads nicht verwenden kann, um die entsprechende Anzahl von Speicherseiten (jede Seitengröße beträgt 8 KB) zuzuweisen Programm. mcache和全局中心缓存 mcentral 上管理超过 64KB 的内存分配,所以对于那些超过 64KB 的内存申请,会直接从堆上(mheap

# 2. Was sind Heap-Speicher und Stapelspeicher?

Je nach verschiedenen Speicherverwaltungsmethoden (Zuweisung und Recycling) kann der Speicher in

Heap-Speicher und Stapelspeicher unterteilt werden.

Was ist also der Unterschied zwischen ihnen?

Heap-Speicher: Der Speicherzuordner und der Garbage Collector sind für das Recycling verantwortlich.

Stapelspeicher: Wird vom Compiler automatisch zugewiesen und freigegeben. Wenn ein Programm ausgeführt wird, kann es mehrere Stapelspeicher geben, aber es gibt sie auf jeden Fall Es wird nur einen Heap-Speicher geben.

Jeder Stapelspeicher wird unabhängig von einem Thread oder einer Coroutine belegt, sodass beim Zuweisen von Speicher vom Stapel keine Sperre erforderlich ist und der Stapelspeicher nach Funktionsende automatisch wiederverwendet wird und die Leistung höher ist als die des Heaps Erinnerung.

Und was ist mit Heap-Speicher? Da mehrere Threads oder Coroutinen gleichzeitig Speicher aus dem Heap anfordern können, erfordert die Beantragung von Speicher im Heap eine Sperre, um Konflikte zu vermeiden, und der Heap-Speicher erfordert den Eingriff von GC (Garbage Collection), wenn die Funktion beendet ist Die Anzahl der GC-Vorgänge beeinträchtigt die Programmleistung erheblich.

# 3.

Die Notwendigkeit einer Escape-AnalyseEs ist ersichtlich, dass zur Verbesserung der Programmleistung die Speicherzuweisung auf dem Heap minimiert werden sollte, was den Druck auf den GC verringern kann.

Bei der Bestimmung, ob einer Variablen Speicher auf dem Heap oder auf dem Stapel zugewiesen wird, liegt es, obwohl der Vorgänger einige Regeln zusammengefasst hat, beim Programmierer, beim Codieren immer auf dieses Problem zu achten, und die Anforderungen an Programmierer sind ziemlich hoch.

Glücklicherweise eröffnet der Go-Compiler auch die Funktion der Escape-Analyse. Mithilfe der Escape-Analyse können Sie alle von Ihrem Programmierer auf dem Heap zugewiesenen Variablen direkt erkennen (dieses Phänomen wird als Escape bezeichnet).

Die Methode besteht darin, den folgenden Befehl auszuführen

go build -gcflags &#39;-m -l&#39; demo.go 

# 或者再加个 -m 查看更详细信息
go build -gcflags &#39;-m -m -l&#39; demo.go
Nach dem Login kopieren

# Die Regeln für den Speicherzuordnungsort

Wenn Sie ein Escape-Analysetool verwenden, können Sie tatsächlich manuell bestimmen, welche Variablen auf dem Heap zugewiesen werden.

Was sind also diese Regeln?

Nach der Zusammenfassung gibt es hauptsächlich vier Situationen wie folgt

  1. Entsprechend dem Verwendungsbereich der Variablen

  2. Entsprechend der Bestimmung des Variablentyps

  3. Entsprechend der belegten Größe der Variablen

  4. Basierend auf Wird die Länge der Variablen bestimmt? Als nächstes analysieren und überprüfen wir sie einzeln

  5. Entsprechend dem Anwendungsbereich der Variablen

Beim Kompilieren wird der Compiler dies tun Führen Sie eine Escape-Analyse durch (Escape-Analyse). Wenn eine Variable gefunden wird Der Verwendungsbereich liegt nur innerhalb der Funktion, dann kann ihr Speicher auf dem Stapel zugewiesen werden.

Zum Beispiel können wir im folgenden Beispiel
func foo() int {
    v := 1024
    return v
}

func main() {
    m := foo()
    fmt.Println(m)
}
Nach dem Login kopieren

go build -gcflags '-m -l' demo.go </code >, um die Ergebnisse der Escape-Analyse anzuzeigen, wobei<code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin -right: 2px ;margin-left: 2px;color: rgb(226, 36, 70);background: rgb(248, 248, 248);">-m dient zum Drucken von Escape-Analyseinformationen, -l deaktiviert die Inline-Optimierung.

Aus den Analyseergebnissen haben wir keine Escape-Anweisungen für die v-Variable gesehen, was darauf hindeutet, dass sie nicht entkommen und auf dem Stapel zugewiesen wurde.

$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:12:13: ... argument does not escape
./demo.go:12:13: m escapes to heap
Nach dem Login kopieren

Und wenn die Variable außerhalb des Funktionsbereichs verwendet werden muss und noch auf dem Stapel zugewiesen ist, wird bei der Rückkehr der Funktion der Speicherplatz, auf den die Variable zeigt, wiederverwendet, und das Programm wird dies zwangsläufig tun Melden Sie einen Fehler, daher können Variablen für diese Art nur auf dem Heap zugewiesen werden.

go build -gcflags &#39;-m -l&#39; demo.go 来查看逃逸分析的结果,其中 -m 是打印逃逸分析的信息,-l 则是禁止内联优化。

从分析的结果我们并没有看到任何关于 v 变量的逃逸说明,说明其并没有逃逸,它是分配在栈上的。

func foo() *int {
    v := 1024
    return &v
}

func main() {
    m := foo()
    fmt.Println(*m) // 1024
}
Nach dem Login kopieren

而如果该变量还需要在函数范围之外使用,如果还在栈上分配,那么当函数返回的时候,该变量指向的内存空间就会被回收,程序势必会报错,因此对于这种变量只能在堆上分配。

比如下边这个例子,返回的是指针

$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:6:2: moved to heap: v
./demo.go:12:13: ... argument does not escape
./demo.go:12:14: *m escapes to heap
Nach dem Login kopieren
Nach dem Login kopieren

从逃逸分析的结果中可以看到 moved to heap: vIm folgenden Beispiel gibt einen Zeiger zurück

func foo() []int {
    a := []int{1,2,3}
    return a
}

func main() {
    b := foo()
    fmt.Println(b)
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜 Escape from Sie können in den Analyseergebnissen sehenmoved to heap: v , die v-Variable ist der vom Heap zugewiesene Speicher , und Es gibt offensichtliche Unterschiede zwischen den oben genannten Szenarien. 🎜
$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:6:2: moved to heap: v
./demo.go:12:13: ... argument does not escape
./demo.go:12:14: *m escapes to heap
Nach dem Login kopieren
Nach dem Login kopieren

除了返回指针之外,还有其他的几种情况也可归为一类:

第一种情况:返回任意引用型的变量:Slice 和 Map

func foo() []int {
    a := []int{1,2,3}
    return a
}

func main() {
    b := foo()
    fmt.Println(b)
}
Nach dem Login kopieren
Nach dem Login kopieren

逃逸分析结果

$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:6:12: []int literal escapes to heap
./demo.go:12:13: ... argument does not escape
./demo.go:12:13: b escapes to heap
Nach dem Login kopieren

第二种情况:在闭包函数中使用外部变量

func Increase() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

func main() {
    in := Increase()
    fmt.Println(in()) // 1
    fmt.Println(in()) // 2
}
Nach dem Login kopieren

逃逸分析结果

$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:6:2: moved to heap: n
./demo.go:7:9: func literal escapes to heap
./demo.go:15:13: ... argument does not escape
./demo.go:15:16: in() escapes to heap
Nach dem Login kopieren

根据变量类型是否确定

在上边例子中,也许你发现了,所有编译输出的最后一行中都是 m escapes to heap

奇怪了,为什么 m 会逃逸到堆上?

其实就是因为我们调用了 fmt.Println() 函数,它的定义如下

func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}
Nach dem Login kopieren

可见其接收的参数类型是 interface{} ,对于这种编译期不能确定其参数的具体类型,编译器会将其分配于堆上。

根据变量的占用大小

最开始的时候,就介绍到,以 64KB 为分界线,我们将内存块分为 小内存块 和 大内存块。

小内存块走常规的 mspan 供应链申请,而大内存块则需要直接向 mheap,在堆区申请。

以下的例子来说明

func foo() {
    nums1 := make([]int, 8191) // < 64KB
    for i := 0; i < 8191; i++ {
        nums1[i] = i
    }
}

func bar() {
    nums2 := make([]int, 8192) // = 64KB
    for i := 0; i < 8192; i++ {
        nums2[i] = i
    }
}
Nach dem Login kopieren

-gcflags 多加个 -m 可以看到更详细的逃逸分析的结果

$ go build -gcflags &#39;-m -l&#39; demo.go 
# command-line-arguments
./demo.go:5:15: make([]int, 8191) does not escape
./demo.go:12:15: make([]int, 8192) escapes to heap
Nach dem Login kopieren

那为什么是 64 KB 呢?

我只能说是试出来的 (8191刚好不逃逸,8192刚好逃逸),网上有很多文章千篇一律的说和 ulimit -a 中的 stack size 有关,但经过了解这个值表示的是系统栈的最大限制是 8192 KB,刚好是 8M。

$ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
Nach dem Login kopieren

我个人实在无法理解这个 8192 (8M) 和 64 KB 是如何对应上的,如果有朋友知道,还请指教一下。

根据变量长度是否确定

由于逃逸分析是在编译期就运行的,而不是在运行时运行的。因此避免有一些不定长的变量可能会很大,而在栈上分配内存失败,Go 会选择把这些变量统一在堆上申请内存,这是一种可以理解的保险的做法。

func foo() {
    length := 10
    arr := make([]int, 0 ,length)  // 由于容量是变量,因此不确定,因此在堆上申请
}

func bar() {
    arr := make([]int, 0 ,10)  // 由于容量是常量,因此是确定的,因此在栈上申请
}
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonEin Artikel erklärt die Speicherzuweisung in Go. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

AI Hentai Generator

AI Hentai Generator

Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

R.E.P.O. Energiekristalle erklärten und was sie tun (gelber Kristall)
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Beste grafische Einstellungen
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. So reparieren Sie Audio, wenn Sie niemanden hören können
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Wie man alles in Myrise freischaltet
1 Monate vor By 尊渡假赌尊渡假赌尊渡假赌

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Apr 19, 2024 am 11:42 AM

In Go umfasst der Funktionslebenszyklus Definition, Laden, Verknüpfen, Initialisieren, Aufrufen und Zurückgeben; der Variablenbereich ist in Funktionsebene und Blockebene unterteilt. Variablen innerhalb einer Funktion sind intern sichtbar, während Variablen innerhalb eines Blocks nur innerhalb des Blocks sichtbar sind .

Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Jun 02, 2024 am 09:00 AM

In Go können Sie reguläre Ausdrücke verwenden, um Zeitstempel abzugleichen: Kompilieren Sie eine Zeichenfolge mit regulären Ausdrücken, z. B. die, die zum Abgleich von ISO8601-Zeitstempeln verwendet wird: ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . Verwenden Sie die Funktion regexp.MatchString, um zu überprüfen, ob eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt.

Wie sende ich Go WebSocket-Nachrichten? Wie sende ich Go WebSocket-Nachrichten? Jun 03, 2024 pm 04:53 PM

In Go können WebSocket-Nachrichten mit dem Paket gorilla/websocket gesendet werden. Konkrete Schritte: Stellen Sie eine WebSocket-Verbindung her. Senden Sie eine Textnachricht: Rufen Sie WriteMessage(websocket.TextMessage,[]byte("message")) auf. Senden Sie eine binäre Nachricht: Rufen Sie WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) auf.

Der Unterschied zwischen Golang und Go-Sprache Der Unterschied zwischen Golang und Go-Sprache May 31, 2024 pm 08:10 PM

Go und die Go-Sprache sind unterschiedliche Einheiten mit unterschiedlichen Eigenschaften. Go (auch bekannt als Golang) ist bekannt für seine Parallelität, schnelle Kompilierungsgeschwindigkeit, Speicherverwaltung und plattformübergreifende Vorteile. Zu den Nachteilen der Go-Sprache gehören ein weniger umfangreiches Ökosystem als andere Sprachen, eine strengere Syntax und das Fehlen dynamischer Typisierung.

Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Jun 04, 2024 pm 12:27 PM

Speicherlecks können dazu führen, dass der Speicher des Go-Programms kontinuierlich zunimmt, indem: Ressourcen geschlossen werden, die nicht mehr verwendet werden, wie z. B. Dateien, Netzwerkverbindungen und Datenbankverbindungen. Verwenden Sie schwache Referenzen, um Speicherlecks zu verhindern, und zielen Sie auf Objekte für die Garbage Collection ab, wenn sie nicht mehr stark referenziert sind. Bei Verwendung von Go-Coroutine wird der Speicher des Coroutine-Stapels beim Beenden automatisch freigegeben, um Speicherverluste zu vermeiden.

Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Apr 18, 2024 pm 03:06 PM

Go-Funktionsdokumentation mit der IDE anzeigen: Bewegen Sie den Cursor über den Funktionsnamen. Drücken Sie den Hotkey (GoLand: Strg+Q; VSCode: Nach der Installation von GoExtensionPack F1 und wählen Sie „Go:ShowDocumentation“).

Wie verwende ich den Fehler-Wrapper von Golang? Wie verwende ich den Fehler-Wrapper von Golang? Jun 03, 2024 pm 04:08 PM

In Golang können Sie mit Fehler-Wrappern neue Fehler erstellen, indem Sie Kontextinformationen an den ursprünglichen Fehler anhängen. Dies kann verwendet werden, um die von verschiedenen Bibliotheken oder Komponenten ausgelösten Fehlertypen zu vereinheitlichen und so das Debuggen und die Fehlerbehandlung zu vereinfachen. Die Schritte lauten wie folgt: Verwenden Sie die Funktion „errors.Wrap“, um die ursprünglichen Fehler in neue Fehler umzuwandeln. Der neue Fehler enthält Kontextinformationen zum ursprünglichen Fehler. Verwenden Sie fmt.Printf, um umschlossene Fehler auszugeben und so mehr Kontext und Umsetzbarkeit bereitzustellen. Wenn Sie verschiedene Fehlertypen behandeln, verwenden Sie die Funktion „errors.Wrap“, um die Fehlertypen zu vereinheitlichen.

Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen May 03, 2024 am 10:54 AM

Das Testen gleichzeitiger Funktionen in Einheiten ist von entscheidender Bedeutung, da dies dazu beiträgt, ihr korrektes Verhalten in einer gleichzeitigen Umgebung sicherzustellen. Beim Testen gleichzeitiger Funktionen müssen grundlegende Prinzipien wie gegenseitiger Ausschluss, Synchronisation und Isolation berücksichtigt werden. Gleichzeitige Funktionen können Unit-Tests unterzogen werden, indem Rennbedingungen simuliert, getestet und Ergebnisse überprüft werden.

See all articles