In Go gibt es verschiedene Möglichkeiten, einen struct
Wert oder einen Slice davon zurückzugeben. Für einzelne habe ich gesehen:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
Ich verstehe die Unterschiede zwischen diesen. Der erste gibt eine Kopie der Struktur zurück, der zweite einen Zeiger auf den innerhalb der Funktion erstellten Strukturwert, der dritte erwartet, dass eine vorhandene Struktur übergeben wird, und überschreibt den Wert.
Ich habe gesehen, dass all diese Muster in verschiedenen Kontexten verwendet werden. Ich frage mich, welche Best Practices diesbezüglich gelten. Wann würden Sie welche verwenden? Zum Beispiel könnte die erste für kleine Strukturen in Ordnung sein (weil der Overhead minimal ist), die zweite für größere. Und die dritte, wenn Sie extrem speichereffizient sein möchten, da Sie eine einzelne Strukturinstanz zwischen Aufrufen problemlos wiederverwenden können. Gibt es Best Practices für die Verwendung von welchen?
Ebenso die gleiche Frage bezüglich Scheiben:
func myfunc() []MyStruct {
return []MyStruct{ MyStruct{Val: 1} }
}
func myfunc() []*MyStruct {
return []MyStruct{ &MyStruct{Val: 1} }
}
func myfunc(s *[]MyStruct) {
*s = []MyStruct{ MyStruct{Val: 1} }
}
func myfunc(s *[]*MyStruct) {
*s = []MyStruct{ &MyStruct{Val: 1} }
}
Nochmals: Was sind hier Best Practices? Ich weiß, dass Slices immer Zeiger sind, daher ist es nicht sinnvoll, einen Zeiger auf ein Slice zurückzugeben. Sollte ich jedoch ein Slice mit Strukturwerten zurückgeben, ein Slice mit Zeigern auf Strukturen, sollte ich einen Zeiger auf ein Slice als Argument übergeben (ein Muster, das in der Go App Engine-API verwendet wird )?
new(MyStruct)
:) Aber es gibt keinen Unterschied zwischen den verschiedenen Methoden zum Zuweisen und Zurückgeben von Zeigern.Antworten:
tl; dr :
Ein Fall, in dem Sie häufig einen Zeiger verwenden sollten:
Einige Situationen, in denen Sie keine Zeiger benötigen:
Richtlinien zur Codeüberprüfung schlagen vor, kleine Strukturen wie
type Point struct { latitude, longitude float64 }
und möglicherweise sogar etwas größere Werte als Werte zu übergeben, es sei denn, die von Ihnen aufgerufene Funktion muss in der Lage sein, sie an Ort und Stelle zu ändern.bytes.Replace
Argumente im Wert von 10 Wörtern (drei Scheiben und eineint
) benötigt.Für Slices müssen Sie keinen Zeiger übergeben, um Elemente des Arrays zu ändern.
io.Reader.Read(p []byte)
ändertp
zum Beispiel die Bytes von . Es ist wohl ein Sonderfall, "kleine Strukturen wie Werte zu behandeln", da Sie intern eine kleine Struktur herumgeben, die als Slice-Header bezeichnet wird (siehe Erklärung von Russ Cox (rsc) ). Ebenso benötigen Sie keinen Zeiger, um eine Karte zu ändern oder auf einem Kanal zu kommunizieren .Für Slices werden Sie neu schneiden (ändern Sie den Start / die Länge / die Kapazität von), integrierte Funktionen wie das
append
Akzeptieren eines Slice-Werts und das Zurückgeben eines neuen. Ich würde das nachahmen; Es vermeidet Aliasing. Wenn Sie ein neues Slice zurückgeben, wird die Aufmerksamkeit auf die Tatsache gelenkt, dass möglicherweise ein neues Array zugewiesen wird, und es ist den Anrufern vertraut.interface{}
Parameter.Zuordnungen, Kanäle, Zeichenfolgen sowie Funktions- und Schnittstellenwerte sind wie Slices interne Referenzen oder Strukturen, die bereits Referenzen enthalten. Wenn Sie also nur vermeiden möchten, dass die zugrunde liegenden Daten kopiert werden, müssen Sie keine Zeiger an sie übergeben . (rsc hat einen separaten Beitrag darüber geschrieben, wie Schnittstellenwerte gespeichert werden ).
flag.StringVar
nehmen eine*string
aus diesem Grunde, zum Beispiel.Wo Sie Zeiger verwenden:
Überlegen Sie, ob Ihre Funktion eine Methode für die Struktur sein soll, auf die Sie einen Zeiger benötigen. Die Menschen erwarten , auf eine Menge von Methoden
x
zu modifizierenx
, so dass die modifizierte Struktur macht der Empfänger Überraschung minimieren kann helfen. Es gibt Richtlinien, wann Empfänger Zeiger sein sollten.Funktionen, die Auswirkungen auf ihre Nicht-Empfänger-Parameter haben, sollten dies im Godoc oder besser noch im Godoc und im Namen (wie
reader.WriteTo(writer)
) verdeutlichen .Sie erwähnen das Akzeptieren eines Zeigers, um Zuordnungen zu vermeiden, indem Sie die Wiederverwendung zulassen. Das Ändern von APIs zum Zwecke der Wiederverwendung von Speicher ist eine Optimierung, die ich verzögern würde, bis klar ist, dass die Zuweisungen nicht triviale Kosten verursachen, und dann nach einer Möglichkeit suchen würde, die nicht allen Benutzern die schwierigere API aufzwingt:
bytes.Buffer
.Reset()
Stellen Sie sich eine Methode vor, um ein Objekt wieder in einen leeren Zustand zu versetzen, wie es einige stdlib-Typen anbieten. Benutzer, die eine Zuordnung nicht interessieren oder nicht speichern können, müssen sie nicht aufrufen.existingUser.LoadFromJSON(json []byte) error
durch umwickelnden könnteNewUserFromJSON(json []byte) (*User, error)
. Auch hier wird die Wahl zwischen Faulheit und Kneifzuweisungen an den einzelnen Anrufer getroffen.sync.Pool
einige Details verarbeiten lassen. Wenn eine bestimmte Zuordnung viel Speicherdruck erzeugt, können Sie sicher sein, dass Sie wissen, wann die Zuordnung nicht mehr verwendet wird und keine bessere Optimierung verfügbarsync.Pool
ist. (CloudFlare hat einen nützlichen (Vor-sync.Pool
) Blog-Beitrag zum Thema Recycling veröffentlicht.)Schließlich, ob Ihre Slices Zeiger sein sollen: Slices von Werten können nützlich sein und Ihnen Zuordnungen und Cache-Fehler ersparen. Es kann Blocker geben:
NewFoo() *Foo
anstatt Go mit dem Wert Null initialisieren zu lassen .append
Elemente, wenn das zugrunde liegende Array vergrößert wird . Zeiger, die Sie vor demappend
Punkt an der falschen Stelle erhalten haben, können für große Strukturen langsamer kopiert werden, und zum Beispiel ist dassync.Mutex
Kopieren nicht zulässig. Einfügen / Löschen in der Mitte und Sortieren verschieben Elemente auf ähnliche Weise.Im Allgemeinen können Wertscheiben sinnvoll sein, wenn Sie entweder alle Ihre Elemente im Voraus platzieren und sie nicht verschieben (z. B. keine
append
s nach der Ersteinrichtung mehr) oder wenn Sie sie weiter verschieben, aber Sie sind sicher, dass dies der Fall ist OK (keine / sorgfältige Verwendung von Zeigern auf Elemente, Elemente sind klein genug, um effizient zu kopieren usw.). Manchmal müssen Sie über die Besonderheiten Ihrer Situation nachdenken oder diese messen, aber das ist eine grobe Richtlinie.quelle
Replace(s, old, new []byte, n int) []byte
; s, alt und neu sind jeweils drei Wörter ( Slice-Header sind(ptr, len, cap)
) undn int
sind ein Wort, also 10 Wörter, was bei acht Bytes / Wort 80 Bytes entspricht.Drei Hauptgründe, warum Sie Methodenempfänger als Zeiger verwenden möchten:
"Erstens und vor allem muss die Methode den Empfänger modifizieren? Wenn dies der Fall ist, muss der Empfänger ein Zeiger sein."
"Zweitens geht es um die Effizienz. Wenn der Empfänger groß ist, beispielsweise eine große Struktur, ist die Verwendung eines Zeigerempfängers viel billiger."
"Als nächstes kommt die Konsistenz. Wenn einige Methoden des Typs Zeigerempfänger haben müssen, sollte dies auch der Rest tun, damit der Methodensatz unabhängig von der Verwendung des Typs konsistent ist."
Referenz: https://golang.org/doc/faq#methods_on_values_or_pointers
Bearbeiten: Eine weitere wichtige Sache ist es, den tatsächlichen "Typ" zu kennen, den Sie an die Funktion senden. Der Typ kann entweder ein 'Werttyp' oder ein 'Referenztyp' sein.
Auch wenn Slices und Maps als Referenzen fungieren, möchten wir sie möglicherweise als Zeiger in Szenarien wie dem Ändern der Länge des Slice in der Funktion übergeben.
quelle
Wenn Sie können (z. B. eine nicht freigegebene Ressource, die nicht als Referenz übergeben werden muss), verwenden Sie einen Wert. Aus folgenden Gründen:
Grund 1 : Sie werden weniger Elemente im Stapel zuweisen. Die Zuweisung / Freigabe vom Stapel erfolgt sofort, die Zuweisung / Freigabe auf dem Heap kann jedoch sehr teuer sein (Zuweisungszeit + Speicherbereinigung). Einige grundlegende Zahlen finden Sie hier: http://www.macias.info/entry/201802102230_go_values_vs_references.md
Grund 2 : Insbesondere wenn Sie zurückgegebene Werte in Slices speichern, werden Ihre Speicherobjekte im Speicher kompakter: Das Schleifen eines Slice, in dem alle Elemente zusammenhängend sind, ist viel schneller als das Iterieren eines Slice, in dem alle Elemente Zeiger auf andere Teile des Speichers sind . Nicht für den Indirektionsschritt, sondern für die Erhöhung von Cache-Fehlern.
Mythosbrecher : Eine typische x86-Cache-Zeile besteht aus 64 Bytes. Die meisten Strukturen sind kleiner als das. Der Zeitpunkt des Kopierens einer Cache-Zeile im Speicher ähnelt dem Kopieren eines Zeigers.
Nur wenn ein kritischer Teil Ihres Codes langsam ist, würde ich eine Mikrooptimierung versuchen und prüfen, ob die Verwendung von Zeigern die Geschwindigkeit etwas verbessert, was die Lesbarkeit und Wartbarkeit beeinträchtigt.
quelle
Ein Fall, in dem Sie im Allgemeinen einen Zeiger zurückgeben müssen, ist das Erstellen einer Instanz einer zustandsbehafteten oder gemeinsam nutzbaren Ressource . Dies geschieht häufig durch Funktionen, denen ein Präfix vorangestellt ist
New
.Da sie eine bestimmte Instanz von etwas darstellen und möglicherweise eine Aktivität koordinieren müssen, ist es nicht sehr sinnvoll, duplizierte / kopierte Strukturen zu generieren, die dieselbe Ressource darstellen. Der zurückgegebene Zeiger fungiert daher als Handle für die Ressource selbst .
Einige Beispiele:
func NewTLSServer(handler http.Handler) *Server
- Instanziieren Sie einen Webserver zum Testenfunc Open(name string) (*File, error)
- Ein Dateizugriffshandle zurückgebenIn anderen Fällen werden Zeiger zurückgegeben, nur weil die Struktur möglicherweise zu groß ist, um standardmäßig kopiert zu werden:
func NewRGBA(r Rectangle) *RGBA
- ein Bild im Speicher zuordnenAlternativ könnte die direkte Rückgabe von Zeigern vermieden werden, indem stattdessen eine Kopie einer Struktur zurückgegeben wird, die den Zeiger intern enthält. Dies wird jedoch möglicherweise nicht als idiomatisch angesehen:
quelle