function - for - inicializar struct golang
¿Receptor de valor frente a receptor de puntero en Golang? (1)
Tenga en cuenta que las preguntas frecuentes no mencionan la coherencia
Lo siguiente es consistencia. Si algunos de los métodos del tipo deben tener receptores de puntero, el resto también debe hacerlo, por lo que el conjunto de métodos es coherente independientemente de cómo se use el tipo. Vea la sección sobre el conjunto de métodos para más detalles.
Como se menciona en este hilo :
La regla sobre punteros vs. valores para receptores es que los métodos de valor pueden invocarse en punteros y valores, pero los métodos de puntero solo se pueden invocar en punteros
Ahora:
¿Puede alguien decirme un caso en el que un receptor de valor claramente tiene más sentido que un receptor de puntero?
El comentario de Code Review puede ayudar:
- Si el receptor es un mapa, func o chan, no use un puntero a él.
- Si el receptor es un corte y el método no resuelve o reasigna el corte, no use un puntero a él.
- Si el método necesita mutar el receptor, el receptor debe ser un puntero.
- Si el receptor es una estructura que contiene un
sync.Mutex
o campo de sincronización similar, el receptor debe ser un puntero para evitar la copia.- Si el receptor es una estructura o matriz grande, un receptor de puntero es más eficiente. ¿Qué tan grande es grande? Supongamos que es equivalente a pasar todos sus elementos como argumentos al método. Si se siente demasiado grande, también es demasiado grande para el receptor.
- ¿Puede la función o los métodos, ya sea concurrentemente o cuando se los llama por este método, mutar el receptor? Un tipo de valor crea una copia del receptor cuando se invoca el método, por lo que las actualizaciones externas no se aplicarán a este receptor. Si los cambios deben estar visibles en el receptor original, el receptor debe ser un puntero.
- Si el receptor es una estructura, matriz o división y cualquiera de sus elementos es un indicador de algo que podría estar mutando, prefiera un receptor de puntero, ya que hará que la intención sea más clara para el lector.
- Si el receptor es una pequeña matriz o estructura que es naturalmente un tipo de valor (por ejemplo, algo así como el tipo
time.Time
), sin campos mutables y sin punteros, o simplemente es un tipo básico simple como int o string, a el receptor de valor tiene sentido .
Un receptor de valor puede reducir la cantidad de basura que se puede generar; si se pasa un valor a un método de valor, se puede usar una copia en la pila en lugar de asignar en el montón. (El compilador intenta ser inteligente para evitar esta asignación, pero no siempre puede tener éxito). No elija un tipo de receptor de valor por este motivo sin primero crear un perfil.- Finalmente, cuando tenga dudas, use un receptor de puntero.
La parte en negrita se encuentra, por ejemplo, en 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) {
...
}
No está muy claro para mí, en cuyo caso me gustaría usar un receptor de valores en lugar de usar siempre un receptor de puntero.
Para recapitular de los documentos:
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
Los documentos también dicen "Para tipos tales como tipos básicos, rebanadas y estructuras pequeñas, un receptor de valor es muy barato, por lo tanto, a menos que la semántica del método requiera un puntero, un receptor de valor es eficiente y claro".
El primer punto dice que es "muy barato", pero la pregunta es más ¿es más barato que el receptor del puntero? Así que hice un pequeño punto de referencia (código en esencia) que me mostró que el receptor de puntero es más rápido incluso para una estructura que tiene un solo campo de cuerda. Estos son los resultados:
// 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
(Editar: Tenga en cuenta que el segundo punto se convirtió en inválido en versiones más recientes, ver comentarios) .
El segundo punto dice que es "eficiente y claro", que es más una cuestión de gusto, ¿no es así? Personalmente, prefiero la coherencia al usar en todas partes de la misma manera. Eficiencia en qué sentido? En cuanto al rendimiento, parece que el puntero es casi siempre más eficiente. Pocas pruebas con una propiedad int mostraron una ventaja mínima del receptor Value (rango de 0.01-0.1 ns / op)
¿Puede alguien decirme un caso en el que un receptor de valor claramente tiene más sentido que un receptor de puntero? O estoy haciendo algo mal en el punto de referencia, ¿pasé por alto otros factores?