Les pointeurs vs les valeurs des paramètres et valeurs de retour
En Aller il y a différentes façons de retourner un struct
valeur ou d'une tranche de celui-ci. Pour les individuels, ceux que j'ai vu:
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 ces. La première renvoie une copie de la structure, le second est un pointeur vers la structure de la valeur créée au sein de la fonction, la troisième s'attend à un existant struct être passé dans et remplace la valeur.
J'ai vu tous ces modèles être utilisée dans différents contextes, je me demandais quelles sont les meilleures pratiques à l'égard de ces. Quand souhaitez-vous utiliser qui? Par exemple, le premier pourrait être ok pour les petites structures (comme l'espace est minime), la seconde pour les plus grands. Et la troisième, si vous voulez être extrêmement efficace en terme de mémoire, parce que vous pouvez facilement réutiliser une struct exemple entre les appels. Existe-il des pratiques exemplaires pour quand utiliser?
De la même façon, 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: quelles sont les meilleures pratiques ici. Je sais que les tranches sont toujours les pointeurs, afin de retourner un pointeur vers une tranche n'est pas utile. Cependant, dois-je retourner une tranche de struct valeurs, une tranche de pointeurs sur les structures, dois-je passer un pointeur à une tranche comme argument (un motif est utilisé dans le Aller à App Engine API)?
- Comme tu dis, ça dépend vraiment du cas d'utilisation. Tous sont valables en fonction de la situation - est-ce un objet mutable? voulons-nous une copie ou un pointeur? etc. BTW, vous n'avez pas mentionné à l'aide de
new(MyStruct)
🙂 Mais il n'y a pas de différence vraiment entre les différentes méthodes d'affectation de pointeurs et de les renvoyer. - C'est-à-dire sur l'ingénierie. Les structures doivent être assez grandes pour que de retourner un pointeur rend votre programme plus rapide. Il suffit de ne pas les déranger, de code, de profil, de fixer, si utile.
- Il n'y a qu'une seule façon de retourner une valeur ou un pointeur, et c'est pour renvoyer une valeur, ou un pointeur. Comment vous allouer est une question distincte. Utilisez ce qui fonctionne pour votre situation, et d'aller écrire un peu de code avant de vous en soucier.
- BTW, juste par curiosité je benchamrked cette. Le retour des structs vs pointeurs semble être à peu près la même vitesse, mais le passage de pointeurs de fonctions en bas de la lignes est siginificantly plus rapide. Bien que n'étant pas sur un niveau, il serait question
- Je suppose que c'est juste bc répartition se fait en dehors de la fonction. Aussi l'analyse comparative des valeurs vs pointeurs dépend de la taille de la structure et de la mémoire des schémas d'accès après le fait. La copie en cache de la ligne de la taille est aussi rapide que vous pouvez obtenir, et de la vitesse de déréférencement de pointeurs de cache du PROCESSEUR est très différente de la référence à partir de la mémoire principale.
- Votre App Engine exemple est intéressant. Ma réponse ci-dessous suggère qu'il doit retourner éventuellement élargi tranche comme
append
. Je pense App Engine ne le fait pas parce qu'il aurait à retourner uninterface{}
(depuis spécifiques struct type n'est connu que de façon dynamique) et les concepteurs ne veulent pas forcer l'utilisateur à taper-faire valoir un retourinterface{}
retour à la spécificité de ce type de tranche, parce que c'est verbeux/maladroit. C'est donc une situation particulière (redimensionnement de la tranche + élément de type non connu au moment de la compilation), qui rend pointeur de la tranche attrayant il. - Connexes: Pourquoi un constructeur d'Aller de l'adresse de retour?
- merci pour l'analyse comparative des! Donc le passage de pointeurs autour est le plus rapide? Je viens de lire : segment.com/blog/... qui dit en utilisant des valeurs rend la collecte des ordures travail plus facile?
- il dépend aussi de garder à l'esprit que ma réponse est près de 6 ans, le moteur d'exécution a changé depuis.
Vous devez vous connecter pour publier un commentaire.
tl;dr:
Un cas où vous devez souvent utiliser un pointeur:
Certaines situations où vous n'avez pas besoin de pointeurs:
Code de révision des lignes directrices suggèrent passant petites structures comme
type Point struct { latitude, longitude float64 }
, et peut-être même des choses un peu plus grand, comme les valeurs, sauf si la fonction que vous appelez doit être en mesure de les modifier en place.bytes.Replace
prend 10 mots de args (trois tranches et unint
).Pour tranches, vous n'avez pas besoin de passer un pointeur pour modifier des éléments de la matrice.
io.Reader.Read(p []byte)
changements les octets dep
, par exemple. C'est sans doute un cas particulier de "traiter peu de structures comme des valeurs", car en interne, vous êtes en passant autour d'une petite structure appelée tranche en-tête (voir Russ Cox (rsc)'explication). De même, vous n'avez pas besoin d'un pointeur à modifier une carte ou communiquer sur un canal.Pour tranches vous pourrez reslice (modifier la start/longueur/capacité de), les fonctions intégrées comme
append
accepter une tranche de la valeur et de retour d'un nouveau. J'avais imiter; ça évite d'aliasing, de retour d'une nouvelle tranche d'aide à attirer l'attention sur le fait qu'un nouveau tableau pourraient être alloués, et il est familier pour les appelants.interface{}
paramètre.Des cartes, des chaînes, des cordes, et la fonction d'interface valeurs, comme des tranches, en interne sont des références ou des structures qui contiennent des références déjà, donc si vous êtes juste essayer d'éviter d'obtenir les données sous-jacentes copié, vous n'avez pas besoin de passer des pointeurs vers eux. (rsc a écrit un post sur la façon de l'interface, les valeurs sont stockées).
drapeau.StringVar
prend un*string
pour cette raison, par exemple.Où vous utiliser des pointeurs:
Examiner si votre fonction doit être une méthode selon la struct vous avez besoin d'un pointeur. Les gens attendent beaucoup de méthodes sur
x
de modifierx
, afin de faire de la modification de la structure du récepteur peut aider à minimiser la surprise. Il y a lignes directrices sur les récepteurs doivent être des pointeurs.Des fonctions qui ont des effets sur leur non-récepteur params doit l'indiquer clairement dans le godoc, ou mieux encore, la godoc et le nom (comme
reader.WriteTo(writer)
).Vous mentionner l'acceptation d'un pointeur pour éviter les allocations en permettant la réutilisation; la modification de l'Api pour l'amour de la mémoire de la réutilisation est une optimisation que j'avais de retard jusqu'à ce qu'il est évident que les allocations ont un coût non négligeable, et puis j'aurais l'air d'un moyen qui n'a pas la force de le plus délicat de l'API sur l'ensemble des utilisateurs:
octets.Tampon
.Reset()
méthode pour mettre un objet dans le vide de l'état, à l'instar de certains stdlib types d'offre. Les utilisateurs qui ne se soucient pas ou ne pouvez pas enregistrer une allocation de ne pas avoir à l'appeler.existingUser.LoadFromJSON(json []byte) error
pourrait être enveloppé parNewUserFromJSON(json []byte) (*User, error)
. Encore une fois, il pousse le choix entre la paresse et de pincer les attributions à la personne de l'appelant.sync.Piscine
gérer certains détails. Si une allocation crée beaucoup de pression sur la mémoire, que vous êtes sûr de savoir quand les alloc n'est plus utilisé, et vous n'avez pas une meilleure optimisation disponibles,sync.Pool
peut vous aider. (CloudFlare publié un utile (pré-sync.Piscine
) post de blog sur le recyclage.)Enfin, si vos tranches de pointeurs: tranches de valeurs peut être utile, et vous épargner les allocations et les défauts de cache. Il peut être bloquants:
NewFoo() *Foo
plutôt que de laisser Aller de l'initialiser avec la la valeur zéro.append
des copies des articles quand il pousse le tableau sous-jacent. Les pointeurs vous obtenu avant que leappend
point à la mauvaise place, après, la copie peut être plus lente pour les énormes structures, par exemplesync.Mutex
la copie n'est pas autorisée. Insérer/supprimer dans le moyen et le tri de même déplacer des éléments autour.Globalement, la valeur des tranches de sens que si l'un de vous débarrasser de tous vos éléments en place et de ne pas les déplacer (par exemple, pas plus
append
s après l'installation initiale), ou si vous ne le garder de les déplacer, mais vous êtes sûr que c'est OK (pas de/attention à l'utilisation de pointeurs sur des objets, les éléments sont suffisamment petits pour copier efficacement, etc.). Parfois, vous avez à penser ni à mesure des spécificités de votre situation, mais c'est un guide approximatif.Replace(s, old, new []byte, n int) []byte
; s, les vieux et les nouveaux sont les trois mots chacun (tranche en-têtes sont(ptr, len, cap)
) etn int
est un seul mot, donc 10 mots, qui, à huit octets/mot de 80 octets.Trois raisons principales lorsque vous souhaitez utiliser la méthode de récepteurs que les pointeurs:
"Première, et la plus importante, est la méthode de la nécessité de modifier le récepteur? Si c'est le cas, le récepteur doit être un pointeur."
"Deuxième est la prise en compte de l'efficacité. Si le récepteur est grande, une grande struct par exemple, il sera beaucoup moins cher d'utiliser un pointeur récepteur."
"Suivant est la cohérence. Si certaines des méthodes du type doit avoir pointeur de récepteurs, le reste doit trop, de sorte que la méthode d'ensemble est cohérent, indépendamment de 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 la véritable "type" que vous envoyez à la fonction. Le type peut être soit une valeur de type " ou "type de référence'.
Même que les tranches et les cartes des actes comme des références, on peut vouloir les passer comme des pointeurs dans des scénarios tels que la modification de la longueur de la tranche dans la fonction.
Cas où vous aurez généralement besoin de retourner un pointeur est lors de la construction d'un instance de certains avec ou ressource commune. Ceci est souvent réalisé par des fonctions préfixées avec
New
.Parce qu'ils représentent une instance spécifique de quelque chose et ils peuvent avoir besoin de coordonner certains d'activité, il ne fait pas beaucoup de sens pour générer, dupliqué, copié structures représentant la même ressource -- de sorte que le pointeur retourné agit comme la poignée de la ressource elle-même.
Quelques exemples:
func NewTLSServer(handler http.Gestionnaire) *Serveur
-- instancier un serveur web pour testerfunc Ouvrir(nom de chaîne) (*Fichier, erreur)
-- retour d'un fichier descripteur d'accèsDans d'autres cas, les pointeurs sont retourné simplement parce que la structure peut être trop grand pour copier par défaut:
func NewRGBA(r Rectangle) *RGBA
-- attribuer une image dans la mémoireSinon, le retour des pointeurs directement pourraient être évités par une au lieu de retourner une copie d'une structure qui contient le pointeur à l'interne, mais peut-être que ce n'est pas considéré comme idiomatiques:
Si vous pouvez (par exemple, un non-ressource partagée qui n'a pas besoin d'être adopté comme référence), utilisez une valeur. Par les raisons suivantes:
Raison 1: vous allez allouer moins d'éléments dans la pile. Allocation/désallocation de la pile est immédiat, mais l'allocation/désallocation sur le Tas peut être très coûteux (moment de l'allocation + de collecte des ordures). 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 les valeurs de retour en tranches, vos objets de mémoire sera plus compact dans la mémoire: boucle une tranche où tous les éléments sont contigus est beaucoup plus rapide que l'itération d'une tranche où tous les éléments sont des pointeurs vers d'autres parties de la mémoire. Pas pour l'indirection étape, mais pour l'augmentation des défauts de cache.
Mythe disjoncteur: un typique x86 ligne de cache de 64 octets. La plupart des structures sont plus petites que ça. Le temps de la copie d'une ligne de cache en mémoire est similaire à la copie d'un pointeur.
Que si une partie critique de votre code est lent je voudrais essayer quelques micro-optimisation et de vérifier si l'utilisation de pointeurs améliore quelque peu la vitesse, au détriment de la lisibilité et de la mantainability.