web-dev-qa-db-fra.com

Récepteur de valeur vs récepteur de pointeur

Je ne sais pas très bien dans quel cas je voudrais utiliser un récepteur de valeur au lieu d'utiliser toujours un récepteur de pointeur.
Pour récapituler à partir des documents:

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

La documentation dit également "Pour les types tels que les types de base, les tranches et les petites structures, un récepteur de valeur est très bon marché, donc à moins que la sémantique de la méthode ne l'exige un pointeur, un récepteur de valeur est efficace et clair. "

Premier point il dit que c'est "très bon marché", mais la question est plus c'est moins cher que le récepteur de pointeur. J'ai donc fait un petit benchmark (code sur Gist) qui m'a montré que le récepteur de pointeur est plus rapide même pour une structure qui n'a qu'un seul champ de chaîne. Voici les résultats:

// Struct one empty string property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  500000000                3.62 ns/op


// Struct one zero int property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  2000000000               0.36 ns/op

(Edit: Veuillez noter que le deuxième point est devenu invalide dans les nouvelles versions go, voir les commentaires).
Deuxième point dit-il, c'est "efficace et clair" qui est plus une question de goût, n'est-ce pas? Personnellement, je préfère la cohérence en utilisant partout de la même manière. L'efficacité dans quel sens? en termes de performances, il semble que les pointeurs soient presque toujours plus efficaces. Peu d'essais avec une propriété int ont montré un avantage minimal du récepteur Value (plage de 0,01 à 0,1 ns/op)

Quelqu'un peut-il me dire un cas où un récepteur de valeur a clairement plus de sens qu'un récepteur de pointeur? Ou est-ce que je fais quelque chose de mal dans l'indice de référence, ai-je négligé d'autres facteurs?

83
Chrisport

Notez que le FAQ mentionne la cohérence

Vient ensuite la cohérence. Si certaines des méthodes du type doivent avoir des récepteurs de pointeur, les autres doivent également l'être, de sorte que l'ensemble de méthodes est cohérent quelle que soit la façon dont le type est utilisé. Voir section sur l'ensemble de méthodes s pour plus de détails.

Comme mentionné dans ce fil :

La règle concernant les pointeurs par rapport aux valeurs pour les récepteurs est que les méthodes de valeur peuvent être appelées sur des pointeurs et des valeurs, mais les méthodes de pointeur ne peuvent être invoquées que sur des pointeurs

À présent:

Quelqu'un peut-il me dire un cas où un récepteur de valeur a clairement plus de sens qu'un récepteur de pointeur?

Le commentaire de révision de code peut aider:

  • Si le récepteur est une carte, func ou chan, n'utilisez pas de pointeur dessus.
  • Si le récepteur est une tranche et que la méthode ne redimensionne pas ou ne réalloue pas la tranche, n'utilisez pas de pointeur dessus.
  • Si la méthode doit muter le récepteur, le récepteur doit être un pointeur.
  • Si le récepteur est une structure qui contient un sync.Mutex Ou un champ de synchronisation similaire, le récepteur doit être un pointeur pour éviter la copie.
  • Si le récepteur est une grande structure ou un tableau, un récepteur de pointeur est plus efficace. Quelle est la taille du grand? Supposons que cela équivaut à passer tous ses éléments en arguments à la méthode. Si cela semble trop grand, c'est aussi trop grand pour le récepteur.
  • La fonction ou les méthodes, soit simultanément soit lorsqu'elles sont appelées à partir de cette méthode, peuvent-elles muter le récepteur? Un type de valeur crée une copie du récepteur lorsque la méthode est invoquée, donc les mises à jour externes ne seront pas appliquées à ce récepteur. Si des modifications doivent être visibles dans le récepteur d'origine, le récepteur doit être un pointeur.
  • Si le récepteur est une structure, un tableau ou une tranche et que l'un de ses éléments est un pointeur vers quelque chose qui pourrait muter, préférez un récepteur de pointeur, car cela rendra l'intention plus claire pour le lecteur.
  • Si le récepteur est un petit tableau ou une structure qui est naturellement un type valeur (par exemple, quelque chose comme le type time.Time), Avec pas de champs mutables et pas de pointeurs, ou est juste un simple type de base tel que int ou chaîne, un récepteur de valeur a du sens .
    Un récepteur de valeur peut réduire la quantité de déchets qui peuvent être générés; si une valeur est transmise à une méthode value, une copie sur pile peut être utilisée au lieu d'allouer sur le tas. (Le compilateur essaie d'être intelligent pour éviter cette allocation, mais il peut ' t toujours réussir.) Ne choisissez pas un type de récepteur de valeur pour cette raison sans profiler d'abord.
  • Enfin, en cas de doute, utilisez un récepteur de pointeur.

La partie en gras se trouve par exemple dans net/http/server.go#Write() :

// Write writes the headers described in h to w.
//
// This method has a value receiver, despite the somewhat large size
// of h, because it prevents an allocation. The escape analysis isn't
// smart enough to realize this function doesn't mutate h.
func (h extraHeader) Write(w *bufio.Writer) {
...
}
101
VonC

Pour ajouter en plus à @VonC une excellente réponse informative.

Je suis surpris que personne n'ait vraiment mentionné les coûts de maintenance une fois que le projet s'agrandit, que les anciens développeurs partent et que le nouveau arrive. Aller est sûrement une langue jeune.

D'une manière générale, j'essaie d'éviter les pointeurs quand je le peux mais ils ont leur place et leur beauté.

J'utilise des pointeurs lorsque:

  • travailler avec de grands ensembles de données
  • avoir un état de maintien de structure, par ex. TokenCache,
    • Je m'assure que TOUS les champs sont PRIVÉS, l'interaction n'est possible que via des récepteurs de méthode définis
    • Je ne transmets cette fonction à aucun goroutin

Par exemple:

type TokenCache struct {
    cache map[string]map[string]bool
}

func (c *TokenCache) Add(contract string, token string, authorized bool) {
    tokens := c.cache[contract]
    if tokens == nil {
        tokens = make(map[string]bool)
    }

    tokens[token] = authorized
    c.cache[contract] = tokens
}

Raisons pour lesquelles j'évite les pointeurs:

  • les pointeurs ne sont pas simultanément sûrs (tout l'intérêt de GoLang)
  • une fois le récepteur de pointeur, toujours le récepteur de pointeur (pour toutes les méthodes de Struct pour la cohérence)
  • les mutex sont sûrement plus chers, plus lents et plus difficiles à entretenir par rapport au "coût de copie de valeur"
  • en parlant de "valeur du coût de copie", est-ce vraiment un problème? L'optimisation prématurée est à l'origine de tous les maux, vous pouvez toujours ajouter des pointeurs plus tard
  • cela me force directement et consciemment à concevoir de petites structures
  • les pointeurs peuvent être évités en concevant des fonctions pures avec une intention claire et des E/S évidentes
  • la collecte des ordures est plus difficile avec des pointeurs, je crois
  • plus facile de discuter de l'encapsulation, des responsabilités
  • restez simple, stupide (oui, les pointeurs peuvent être difficiles car vous ne connaissez jamais le développement du prochain projet)
  • les tests unitaires sont comme marcher dans un jardin rose (expression slovaque seulement?), signifie facile
  • aucun NIL si les conditions (NIL peuvent être passées là où un pointeur était attendu)

Ma règle d'or, écrivez autant de méthodes encapsulées que possible telles que:

package rsa

// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(Rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
    return []byte("secret text"), nil
}

cipherText, err := rsa.EncryptPKCS1v15(Rand, pub, keyBlock) 

MISE À JOUR:

Cette question m'a inspiré à rechercher plus sur le sujet et à écrire un blog à ce sujet https://medium.com/gophersland/Gopher-vs-object-oriented-golang-4fa62b88c701

11
BlocksByLukas