Heim  >  Artikel  >  Backend-Entwicklung  >  Wissen Sie, wann Sie Go-Zeiger verwenden sollten?

Wissen Sie, wann Sie Go-Zeiger verwenden sollten?

藏色散人
藏色散人nach vorne
2021-09-24 15:08:563010Durchsuche

Dieser Artikel wird von der Go-SpracheTutorial-Kolumne bereitgestellt, um Ihnen vorzustellen, unter welchen Umständen der Go-Pointer (Go Pointer) verwendet wird. Ich hoffe, dass er Freunden in Not hilfreich sein wird!

Ich denke, eines der größten Missverständnisse bei der Verwendung von Zeigern besteht darin, dass Zeiger in Go den Zeigern in C sehr ähnlich sind. Dies ist jedoch nicht der Fall. Zeiger funktionieren in Go nicht auf die gleiche Weise wie in C/C++. Wissen Sie, wann Sie Go-Zeiger verwenden sollten?

In diesem Artikel wird erläutert, wie Sie Go-Zeiger richtig verwenden.

Falsche Schlussfolgerung: Zeiger zu verwenden ist besser?

Es wird allgemein angenommen, dass Anwendungen schneller ausgeführt werden, wenn Zeiger verwendet werden, da dadurch das ständige Kopieren von Werten vermieden wird. Es ist keine Überraschung, dass wir bei Go die gleiche Idee haben.

Allerdings ist die Zeigerübergabe in

Go im Allgemeinen langsamer als die Wertübergabe

. Dies ist eine Folge davon, dass Go eine durch Müll gesammelte Sprache ist. Wenn Sie einen Zeiger auf eine Funktion übergeben, muss Go eine Escape-Analyse durchführen, um zu bestimmen, ob die Variable auf dem Heap oder dem Stack gespeichert werden soll. Dies führt bereits zu zusätzlichem Overhead, ansonsten können die Variablen jedoch im Heap gespeichert werden. Wenn Sie eine Variable im Heap speichern, verlieren Sie auch Zeit, während der GC ausgeführt wird.

Eine praktische Funktion von Go ist, dass Sie überprüfen können, was die Escape-Analyse bewirkt hat, indem Sie den Befehl go build -gcflags="-m" ausführen. Wenn Sie dies tun, teilt Ihnen Go mit, ob eine Variable auf den Heap entkommt:

./main.go:44:20: greet ... argument does not escape
./main.go:44:21: greeting escapes to heap
./main.go:44:21: name escapes to heap
Wenn eine Variable nicht auf den Heap entkommt, befindet sie sich auf dem Stapel. Der Stack benötigt keinen Garbage Collector zum Löschen von Variablen, er führt nur Push/Pop-Vorgänge aus. Wenn Inhalte als Wert übergeben werden, werden sie immer auf dem Stapel verarbeitet, was keinen Mehraufwand bei der Speicherbereinigung verursacht. (Der GC wird standardmäßig ausgeführt. Weniger Inhalt im Heap bedeutet, dass der GC weniger zu tun hat.)

go build -gcflags="-m" 来检查逃逸分析做了什么。如果你这样做,Go 将告诉你一个变量是否逃到堆上:

type person struct {
 name string
}func main() {
 p := person{"Richard"}
 rename(p)
 fmt.Println(p)
}func rename(p person) {
 p.name = "test"
}

如果一个变量没有逃逸到堆中,它就在栈中。栈是不需要垃圾回收器来清除变量的,它只做 push/pop 操作。

如果任何内容都进行值传递,那么将一直在栈中做相关处理,这不会带来垃圾回收方面的开销。(GC 将按默认设置运行。堆中内容越少使得 GC 需要做的事情也越少)。

现在你知道了吧,使用指针反而会降低性能,那么什么时候需要使用指针呢?

拷贝大的数据结构

指针是否一直表现的比值传递差呢?显然不是这样的。对大的数据结构进行处理时,指针将发挥作用。这样可能会使得垃圾回收的开销被拷贝大量数据的开销抵消掉。

当我提到这点时,总是被问到‘那个大数据应该多大’?

我觉得这里没有一个固定的数值,凡是与性能相关的,都应该对其进行基准测试。 Go 有内置的强大的基准测试工具,完全可以利用起来  

可变性

唯一能修改函数参数的方式是传指针。默认对值的修改都是在副本上进行的。因此这些修改不能在调用它的函数中体现。

看下面的代码:

func main() {
 p := person{"Richard"}
 rename(&p)
 fmt.Println(p)
}func rename(p *person) {
 p.name = "test"
}

输出是 Richard ,因为对 person 的修改是在它的副本上进行的。如果要改变底层 person 对象的值,需要使用指针。

func (p *person) rename(s string) {
   p.name = s 
}func (p *person) printName() {
  fmt.Println(p.name)
}

如上,输出 test 。可变性是指针在 Go 中使用的一种情景。这是否是好事,还需要讨论。

API 一致性

使用指针可以维持最新值。这可以保持 API 一致性,即使不是所有的方法都改变它的值。

因此,这个:

func (p *person) rename(s string) {
   p.name = s 
}func (p person) printName() {
  fmt.Println(p.name)
}

优于

type exam struct {
    score   int
    present bool
}

虽然为了一致性并不需要在 printName 中使用指针。但是这将使得 API 更简单,避免去记到底哪里需要引用。

表示缺失

一般值在使用时,具有默认零值。但有些情景需要知道某个事物是缺少或未填充值。例如一个结构体包含学生的考试分数,如果结构体是空且有分数 0 ,这表示这个学生考的不好,还是压根没有参加考试呢?

指针的默认零值是 nil 指针,表示没有设置值。也可以像下面这样实现这种要求:

func main() {
 p := person{"richard"}
 p = rename(p)
 fmt.Println(p)
}func rename(p person) person {
 p.name = "test"
 return p
}

使用单独的 presentJetzt wissen Sie, dass die Verwendung von Zeigern die Leistung verringert. Wann

brauchen

Zeiger?

Kopieren großer DatenstrukturenSind Zeiger immer schlechter als die Wertübertragung? Das ist offensichtlich nicht der Fall. Bei der Arbeit mit großen Datenstrukturen kommen Zeiger ins Spiel. Dies kann dazu führen, dass die Kosten für die Speicherbereinigung durch die Kosten für das Kopieren großer Datenmengen ausgeglichen werden.

Wenn ich das erwähne, werde ich immer gefragt: „Wie groß sollen diese Big Data sein?“

Ich denke, hier gibt es keinen festen Wert, alles, was mit der Leistung zu tun hat, sollte einem Benchmarking unterzogen werden. Go verfügt über integrierte leistungsstarke Benchmarking-Tools, die voll ausgenutzt werden können.

🎜Variabilität🎜🎜Die einzige Möglichkeit, Funktionsparameter zu ändern, besteht darin, Zeiger zu übergeben. Standardmäßig werden alle Änderungen an Werten in der Kopie durchgeführt. Daher können diese Änderungen nicht in der Funktion widergespiegelt werden, die sie aufruft. 🎜🎜Schauen Sie sich den Code unten an: 🎜
x := []int{1,2}
x = append(x, 3)
x = append(x, 4)
🎜Die Ausgabe lautet Richard, da die Änderungen an person in der Kopie vorgenommen werden. Wenn Sie den Wert des zugrunde liegenden Personenobjekts ändern möchten, müssen Sie einen Zeiger verwenden. 🎜rrreee🎜Wie oben, geben Sie test aus. Veränderlichkeit ist eine Situation, in der in Go Zeiger verwendet werden. Ob das eine gute Sache ist, steht zur Debatte. 🎜🎜API-Konsistenz🎜🎜Verwenden Sie Zeiger, um den neuesten Wert beizubehalten. Dadurch bleibt die API konsistent, auch wenn nicht alle Methoden ihren Wert ändern. 🎜🎜Also, das: 🎜rrreee🎜 ist besser als 🎜rrreee🎜, obwohl aus Konsistenzgründen keine Notwendigkeit besteht, Zeiger in printName zu verwenden. Dadurch wird die API jedoch einfacher und Sie müssen sich nicht mehr merken, wo genau eine Referenz benötigt wird. 🎜🎜 weist auf fehlende 🎜🎜Allgemeine Werte haben bei Verwendung den Standardwert Null. Es gibt jedoch Situationen, in denen Sie wissen müssen, dass etwas fehlt oder einen unbefüllten Wert hat. Wenn die Struktur beispielsweise leer ist und eine Punktzahl von 0 aufweist, bedeutet dies, dass der Schüler im Test nicht gut abgeschnitten hat oder den Test überhaupt nicht bestanden hat. 🎜🎜Der Standard-Nullwert eines Zeigers ist ein nil-Zeiger, was bedeutet, dass kein Wert festgelegt ist. Diese Anforderung kann auch wie folgt umgesetzt werden: 🎜rrreee🎜Verwenden Sie ein separates Feld vorhanden, um anzugeben, dass der Student die Prüfung nicht abgelegt hat. 🎜🎜Warum habe ich mich für den Wert entschieden? 🎜🎜🎜Das ist etwas subjektiv. Unterschiedliche Menschen haben unterschiedliche Programmierverständnisse, daher verlangen wir nicht, dass jeder das gleiche Konzept hat. 🎜🎜🎜Ich glaube, dass es sinnvoll ist, so viele Standardwerte wie möglich in Go-Werten zu haben. Das gilt vielleicht nicht für alle Szenarien, aber in meinem Fall hat es einen großen Unfall verhindert. Die Verwendung eines Werts anstelle eines Zeigers führt aufgrund eines Nullzeigers nicht zu Tony Hoares „Millionen-Dollar-Fehler“. 🎜🎜Der Standardwert Null ist nützlich, um viele Deklarationen zu vermeiden. 🎜

另一个好处是易变性造成的问题比它解决的问题多的得多。易变性给函数带来的副作用同时使得调试变得更加困难。 通过让函数返回修改之后的结构体,可以避免这种突变。

重写之前的例子

func main() {
 p := person{"richard"}
 p = rename(p)
 fmt.Println(p)
}func rename(p person) person {
 p.name = "test"
 return p
}

这也是 append 如何工作的,所以并不陌生。

x := []int{1,2}
x = append(x, 3)
x = append(x, 4)

鉴于指针的安全性,和值处理比指针处理更快,使用指针需要反复斟酌。

原文地址:https://medium.com/@meeusdylan/when-to-use-pointers-in-go-44c15fe04eac

译文地址:https://learnku.com/go/t/60923

Das obige ist der detaillierte Inhalt vonWissen Sie, wann Sie Go-Zeiger verwenden sollten?. 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