Dans Go, il existe différentes façons de renvoyer un struct
valeur ou une tranche de celle-ci. Pour les individus que j'ai vus:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
Je comprends les différences entre ceux-ci. Le premier retourne une copie de la structure, le second un pointeur sur la valeur de structure créée dans la fonction, le troisième s'attend à ce qu'une structure existante soit transmise et remplace la valeur.
J'ai vu tous ces modèles être utilisés dans divers contextes, je me demande quelles sont les meilleures pratiques à ce sujet. Quand utiliseriez-vous quoi? Par exemple, le premier pourrait être correct pour les petites structures (car la surcharge est minime), la seconde pour les plus grandes. Et le troisième si vous voulez être extrêmement efficace en mémoire, car vous pouvez facilement réutiliser une seule instance de structure entre les appels. Existe-t-il des meilleures pratiques pour savoir quand les utiliser?
De même, la même question concernant les tranches:
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} }
}
Encore une fois: quelles sont les meilleures pratiques ici. Je sais que les tranches sont toujours des pointeurs, donc retourner un pointeur sur une tranche n'est pas utile. Cependant, dois-je renvoyer une tranche de valeurs de struct, une tranche de pointeurs vers des structures, dois-je passer un pointeur vers une tranche comme argument (un modèle utilisé dans l' API Go App Engine )?
new(MyStruct)
:) Mais il n'y a pas vraiment de différence entre les différentes méthodes d'allocation des pointeurs et de leur retour.Réponses:
tl; dr :
Un cas où vous devriez souvent utiliser un pointeur:
Certaines situations où vous n'avez pas besoin de pointeurs:
Les directives de révision de code suggèrent de passer de petites structures comme
type Point struct { latitude, longitude float64 }
, et peut-être même des choses un peu plus grandes, en tant que valeurs, à moins que la fonction que vous appelez ne puisse les modifier en place.bytes.Replace
prend 10 mots d'arguments (trois tranches et unint
).Pour les tranches , vous n'avez pas besoin de passer un pointeur pour changer les éléments du tableau.
io.Reader.Read(p []byte)
change les octets dep
, par exemple. C'est sans doute un cas spécial de "traiter les petites structures comme des valeurs", car en interne, vous passez autour d'une petite structure appelée un en- tête de tranche (voir l' explication de Russ Cox (rsc) ). De même, vous n'avez pas besoin d'un pointeur pour modifier une carte ou communiquer sur un canal .Pour les tranches, vous allez redimensionner (changer le début / la longueur / la capacité de), les fonctions intégrées comme
append
accepter une valeur de tranche et en retourner une nouvelle. J'imiterais ça; cela évite le crénelage, le retour d'une nouvelle tranche permet d'attirer l'attention sur le fait qu'un nouveau tableau peut être alloué, et il est familier aux appelants.interface{}
paramètre.Les cartes, les canaux, les chaînes et les valeurs de fonction et d'interface , comme les tranches, sont des références internes ou des structures qui contiennent déjà des références, donc si vous essayez simplement d'éviter de copier les données sous-jacentes, vous n'avez pas besoin de leur passer des pointeurs . (rsc a écrit un article séparé sur la façon dont les valeurs d'interface sont stockées ).
flag.StringVar
prend un*string
pour cette raison, par exemple.Où vous utilisez des pointeurs:
Déterminez si votre fonction doit être une méthode sur la structure vers laquelle vous avez besoin d'un pointeur. Les gens attendent beaucoup de méthodes sur
x
de modifierx
, rendant ainsi le struct modifié le récepteur peut aider à minimiser la surprise. Il existe des directives sur le moment où les récepteurs doivent être des pointeurs.Les fonctions qui ont des effets sur leurs paramètres non récepteurs devraient l'indiquer clairement dans le godoc, ou mieux encore, le godoc et le nom (comme
reader.WriteTo(writer)
).Vous mentionnez accepter un pointeur pour éviter les allocations en permettant la réutilisation; changer les API pour la réutilisation de la mémoire est une optimisation que je retarderais jusqu'à ce qu'il soit clair que les allocations ont un coût non trivial, puis je chercherais un moyen qui ne force pas l'API la plus délicate sur tous les utilisateurs:
bytes.Buffer
.Reset()
méthode pour remettre un objet dans un état vide, comme certains types stdlib le proposent. Les utilisateurs qui ne se soucient pas ou ne peuvent pas enregistrer une allocation ne doivent pas l'appeler.existingUser.LoadFromJSON(json []byte) error
pourrait être encapsulé parNewUserFromJSON(json []byte) (*User, error)
. Encore une fois, cela pousse le choix entre la paresse et le pincement des allocations à l'appelant individuel.sync.Pool
gérer certains détails. Si une allocation particulière crée beaucoup de pression sur la mémoire, vous êtes certain de savoir quand l'allocation n'est plus utilisée et que vous ne disposez pas d'une meilleure optimisation, celasync.Pool
peut vous aider. (CloudFlare a publié unsync.Pool
article de blog (pré- ) utile sur le recyclage.)Enfin, si vos tranches doivent être des pointeurs: les tranches de valeurs peuvent être utiles et vous épargner des allocations et des ratés de cache. Il peut y avoir des bloqueurs:
NewFoo() *Foo
plutôt que de laisser Go s'initialiser avec la valeur zéro .append
copie les éléments lorsqu'il agrandit le tableau sous-jacent . Les pointeurs que vous avez reçus avant leappend
point au mauvais endroit après, la copie peut être plus lente pour les structures énormes, et par exemple, lasync.Mutex
copie n'est pas autorisée. Insérer / supprimer au milieu et trier de la même manière déplacer les éléments.De manière générale, les tranches de valeur peuvent avoir un sens si vous mettez tous vos éléments en place à l'avant et ne les déplacez pas (par exemple, plus de
append
s après la configuration initiale), ou si vous continuez à les déplacer, mais vous êtes sûr que c'est OK (pas / utilisation prudente des pointeurs vers les éléments, les éléments sont assez petits pour être copiés efficacement, etc.). Parfois, vous devez penser ou mesurer les spécificités de votre situation, mais c'est un guide approximatif.la source
Replace(s, old, new []byte, n int) []byte
; s, ancien et nouveau sont chacun trois mots (les en- têtes de tranche le sont(ptr, len, cap)
) etn int
est un mot, donc 10 mots, ce qui à huit octets / mot équivaut à 80 octets.Trois raisons principales pour lesquelles vous souhaitez utiliser des récepteurs de méthode comme pointeurs:
"Premièrement, et le plus important, la méthode doit-elle modifier le récepteur? Si c'est le cas, le récepteur doit être un pointeur."
"Le deuxième est la considération de l'efficacité. Si le récepteur est grand, une grande structure par exemple, il sera beaucoup moins cher d'utiliser un récepteur de pointeur."
"Ensuite est la cohérence. Si certaines des méthodes du type doivent avoir des récepteurs de pointeur, le reste devrait aussi, donc l'ensemble de méthodes est cohérent quelle que soit la façon dont le type est utilisé"
Référence : https://golang.org/doc/faq#methods_on_values_or_pointers
Edit: Une autre chose importante est de connaître le "type" réel que vous envoyez pour fonctionner. Le type peut être soit un «type de valeur» soit un «type de référence».
Même si les tranches et les cartes agissent comme des références, nous pourrions vouloir les passer comme pointeurs dans des scénarios comme changer la longueur de la tranche dans la fonction.
la source
Un cas où vous devez généralement renvoyer un pointeur est lors de la construction d'une instance d'une ressource avec état ou partageable . Cela se fait souvent par des fonctions préfixées par
New
.Parce qu'ils représentent une instance spécifique de quelque chose et qu'ils peuvent avoir besoin de coordonner une certaine activité, cela n'a pas beaucoup de sens de générer des structures dupliquées / copiées représentant la même ressource - donc le pointeur retourné agit comme le handle de la ressource elle-même .
Quelques exemples:
func NewTLSServer(handler http.Handler) *Server
- instancier un serveur web pour testerfunc Open(name string) (*File, error)
- retourner un descripteur d'accès aux fichiersDans d'autres cas, les pointeurs sont renvoyés simplement parce que la structure peut être trop grande pour être copiée par défaut:
func NewRGBA(r Rectangle) *RGBA
- allouer une image en mémoireAlternativement, le retour direct de pointeurs pourrait être évité en renvoyant à la place une copie d'une structure qui contient le pointeur en interne, mais ce n'est peut-être pas considéré comme idiomatique:
la source
Si vous le pouvez (par exemple, une ressource non partagée qui n'a pas besoin d'être transmise comme référence), utilisez une valeur. Pour les raisons suivantes:
Raison 1 : vous allouerez moins d'articles dans la pile. L'allocation / désallocation à partir de la pile est immédiate, mais l'allocation / désallocation sur le tas peut être très coûteuse (temps d'allocation + ramasse-miettes). Vous pouvez voir quelques chiffres de base ici: http://www.macias.info/entry/201802102230_go_values_vs_references.md
Raison 2 : surtout si vous stockez des valeurs retournées dans des tranches, vos objets mémoire seront plus compactés en mémoire: boucler une tranche où tous les éléments sont contigus est beaucoup plus rapide que d'itérer une tranche où tous les éléments sont des pointeurs vers d'autres parties de la mémoire . Pas pour l'étape d'indirection mais pour l'augmentation des échecs de cache.
Briseur de mythes : une ligne de cache x86 typique fait 64 octets. La plupart des structures sont plus petites que cela. Le temps de copie d'une ligne de cache en mémoire est similaire à la copie d'un pointeur.
Ce n'est que si une partie critique de votre code est lente que j'essaierais une micro-optimisation et vérifier si l'utilisation de pointeurs améliore quelque peu la vitesse, au prix d'une moindre lisibilité et d'une maintenabilité.
la source