Valore ricevitore vs puntatore ricevitore

È molto chiaro per me, nel qual caso vorrei utilizzare un valore ricevitore invece di usare sempre un puntatore ricevitore.
Per ricapitolare dal docs:

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

Il docs dice anche “Per i tipi di tipi di base, a fettine, e le piccole strutture, un valore ricevitore è molto basso quindi, a meno che la semantica del metodo richiede un puntatore, un valore ricevitore è efficiente e trasparente.”

Primo punto si dice che è “molto basso”, ma il problema non si è più conveniente quindi il puntatore del ricevitore. Così ho fatto un piccolo benchmark (codice in materia di gist) che mi ha mostrato, che il puntatore del ricevitore è più veloce anche per una struttura che ha un solo campo di tipo stringa. Questi sono i risultati:

//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: si Prega di notare che il secondo punto è valido nelle versioni più recenti di andare versioni, vedi commenti) .
Secondo punto si dice, è “efficiente e trasparente”, che è più una questione di gusto, non è vero? Personalmente preferisco la coerenza utilizzando dappertutto stesso modo. Efficienza in che senso? prestazioni saggio sembra puntatore sono quasi sempre più efficiente. Alcuni test viene eseguito con un int proprietà ha mostrato il minimo vantaggio di Valore ricevitore (range 0.01-0.1 ns/op)

Qualcuno può dirmi un caso in cui un valore ricevitore chiaramente più senso quindi un puntatore ricevitore? O sto facendo qualcosa di sbagliato nel benchmark, ho trascurato altri fattori?

  • Mi sono imbattuto simili parametri di riferimento con una singola stringa di campo e anche con due campi: string, int campi. Ho ottenuto risultati più velocemente dal valore del ricevitore. BenchmarkChangePointerReceiver-4 10000000000 0.99 ns/op BenchmarkChangeItValueReceiver-4 10000000000 0.33 ns/op utilizzando 1,8. Mi chiedo se ci sono ottimizzazioni del compilatore fatto dopo l’ultima esecuzione del benchmark di riferimento. Vedere gist per maggiori dettagli.
  • Hai ragione. In esecuzione il mio originale benchmark utilizzando Go1.9, ottengo risultati diversi ora. Puntatore Ricevitore 0.60 ns/op, Valore ricevitore 0.38 ns/op
InformationsquelleAutor Chrisport | 2015-01-05

 

2 Replies
  1. 101

    Nota che le FAQ non parlare di coerenza

    Successivo è la coerenza. Se alcuni dei metodi di tipo puntatore ricevitori, il resto dovrebbe troppo, in modo che il metodo è coerente, indipendentemente da come il tipo utilizzato. Vedere la sezione sul metodos per i dettagli.

    Come accennato in questo thread:

    La regola sui puntatori vs valori per i ricevitori è che il valore metodi
    essere richiamato su puntatori e valori, ma puntatore metodi possono essere invocati solo
    su puntatori

    Ora:

    Qualcuno può dirmi un caso in cui un valore ricevitore chiaramente più senso quindi un puntatore ricevitore?

    Il La Revisione del codice commento può aiutare:

    • Se il ricevitore è una mappa, func o chan, non utilizzare un puntatore ad esso.
    • Se il ricevitore è una fetta e il metodo non reslice o riallocare la fetta, non utilizzare un puntatore ad esso.
    • Se il metodo deve mutare il ricevitore, il ricevitore deve essere un puntatore.
    • Se il ricevitore è una struttura che contiene un sync.Mutex o simili sincronizzazione campo, il ricevitore deve essere un puntatore per evitare di copiare.
    • Se il ricevitore è una grande struttura o di una matrice, un puntatore ricevitore è più efficiente. Quanto è grande? Supporre che è equivalente al passaggio di tutti i suoi elementi come parametri del metodo. Se si sente troppo grande, è anche troppo grande per il ricevitore.
    • Può funzionare o i metodi contemporaneamente o quando chiamato da questo metodo, essere mutando il ricevitore? Un tipo di valore si crea una copia del ricevitore quando il metodo viene richiamato, quindi gli aggiornamenti non saranno applicate a questo ricevitore. Se le modifiche devono essere visibili in originale ricevitore, il ricevitore deve essere un puntatore.
    • Se il ricevitore è una struct, array o di una fetta di uno dei suoi elementi è un puntatore a qualcosa che potrebbe essere in mutazione, preferiscono un puntatore ricevitore, come farà l’intenzione più chiaro al lettore.
    • Se il ricevitore è un piccolo array o di una struttura, che è naturalmente un tipo di valore (per esempio, qualcosa come il time.Time tipo), senza mutevole campi e non puntatori, o è solo una semplice base di tipo int o stringa, un valore ricevitore senso.

      Un valore ricevitore è in grado di ridurre la quantità di rifiuti che può essere generato; se un valore è passato da un valore di metodo, una pila di copia può essere utilizzato invece di allocazione su heap. (Il compilatore cerca di essere intelligente su come evitare questo tipo di allocazione, ma non sempre con successo.) Non scegliere un valore di tipo di ricevitore per questo motivo, senza profilatura prima.
    • Infine, in caso di dubbio, utilizzare un puntatore ricevitore.

    La parte in grassetto è, ad esempio, in net/http/server.vai#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) {
    ...
    }
    • Grazie per la risposta dettagliata e riferimenti.
    • The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers Non è vero, in realtà. Sia il valore che il ricevitore e il puntatore ricevitore metodi possono essere invocati su un correttamente digitato il puntatore o non-puntatore. Indipendentemente da ciò che viene chiamato il metodo all’interno del corpo del metodo l’identificatore del ricevitore si riferisce ad una copia del valore durante un valore ricevitore viene utilizzato, e un puntatore a un puntatore ricevitore viene utilizzato: Vedi play.golang.org/p/3WHGaAbURM
    • C’è una grande spiegazione qui “Se x è indirizzabile e &x con il metodo set contiene m, x.m() è l’abbreviazione per&x).m(). “
    • Sì: che è stato discusso a stackoverflow.com/q/43953187/6309
    • Grande risposta, ma ho il forte dissagree con questo punto: “come farà l’intenzione più chiaro”, NO, per un pulito API, X come argomento e Y come valore di ritorno è un chiaro intento. Il passaggio di una Struct dal puntatore e passare il tempo a leggere attentamente il codice per verificare che tutti gli attributi vengono modificati è molto chiaro e gestibile.
    • Concordato. Ho con voto positivo la tua risposta.

  2. 11

    Per aggiungere inoltre a @VonC grande, informativo risposta.

    Sono sorpreso che nessuno ha menzionato la manutenzione costo una volta che il progetto diventa più grande, vecchio devs lasciare e nuovo viene. Vai sicuramente è un giovane lingua.

    In generale, cerco di evitare di puntatori quando posso, ma hanno il loro posto e la bellezza.

    Io uso i puntatori quando:

    • di lavoro con grandi set di dati
    • hanno una struttura che mantiene lo stato, ad esempio, TokenCache,
      • Ho assicurarsi che TUTTI i campi sono PRIVATE, l’interazione è possibile solo attraverso il metodo definito ricevitori
      • Non mi passa questa funzione per qualsiasi goroutine

    E. g:

    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
    }

    Motivi per cui evito di puntatori:

    • i puntatori non sono contemporaneamente cassetta di sicurezza (il punto di GoLang)
    • una volta che il puntatore del ricevitore, sempre puntatore del ricevitore (per tutti Struct metodi per coerenza)
    • i mutex sono sicuramente più costosi, più lento e più difficile da mantenere in confronto al valore di “costo copia”
    • parlando di “valore di costo copia”, è davvero un problema? Prematura di ottimizzazione è la radice di ogni male, è sempre possibile aggiungere i puntatori in seguito
    • direttamente, inconsciamente mi costringe a creare piccole Strutture
    • i puntatori possono essere in gran parte evitato la progettazione di funzioni pure con la chiara intenzione e ovvio I/O
    • garbage collection è più difficile con i puntatori credo
    • più facile parlare di incapsulamento, responsabilità
    • keep it simple, stupid (sì, puntatori può essere difficile perché non si sa mai che il prossimo progetto dev)
    • unit testing è come camminare attraverso il giardino rosa (slovacco è solo espressione?), facile
    • non NULLO se le condizioni (NIL può essere passato in cui un puntatore era previsto)

    Mia regola, scrivere molti incapsulato metodi possibili, quali:

    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) 

    AGGIORNAMENTO:

    Questa domanda mi ha ispirato a fare una ricerca sull’argomento di più e scrivere un post sul blog su di esso https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701

    • Interessante aggiunta alla mia risposta. +1
    • Oh da VonC stesso! Onore a sir 🙂 Apprezzato!
    • L’onore è mio. Sto ancora imparando. Ogni giorno.

Lascia un commento