2015-01-05 6 views
34

Это очень неясно для меня, и в этом случае я хотел бы использовать приемник значений вместо того, чтобы всегда использовать приемник указателя.
Резюмируя из документации:Приемник-приемник по отношению к приемнику указателя в Голанге?

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 

The документов говорит также «Для таких типов, как основные типов, ломтики и малые структуры, значение приемника очень дешево, поэтому, если семантика методы требует указатель, приемник значений эффективен и понятен ».

Первый пункт говорит, что это «очень дешево», но вопрос в том, что он дешевле, чем приемник указателя. Поэтому я сделал небольшой тест (code on gist), который показал мне, что приемник указателя быстрее даже для структуры, которая имеет только одно поле строки. Таковы результаты:

// 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: Обратите внимание, что второй пункт стал недействительным в новых версиях отыщите комментариев).
Второй пункт Он говорит, что это «эффективный и понятный», который больше зависит от вкуса, не так ли? Лично я предпочитаю согласованность, используя везде одинаково. Эффективность в каком смысле? Похоже, что указатель почти всегда более эффективен. Несколько тестовых прогонов с одним свойством int показали минимальное преимущество приемника значений (диапазон 0,01-0,1 нс/оп)

Может ли кто-нибудь сказать мне случай, когда приемник значений явно имеет больше смысла, чем приемник указателя? Или я делаю что-то неправильно в бенчмарке, не упустил ли я другие факторы?

+2

Я побежал аналогичные тесты с одного поля строки, а также с двумя полями: строка и INT поля. Я получаю более быстрые результаты от приемника значений. BenchmarkChangePointerReceiver-4 \t \t 10000000000 0,99 нс/оп BenchmarkChangeItValueReceiver-4 0,33 нс/оп Это использование Go 1.8. Интересно, были ли оптимизаторы компилятора сделаны с момента последнего запуска тестов. Дополнительную информацию см. В [gist] (https://gist.github.com/pbitty/aaf143ce98907eb1687bc0eff28a436b). – pbitty

+0

Вы правы. Запуск моего исходного теста с использованием Go1.9, теперь я получаю разные результаты. Pointer Receiver 0.60 ns/op, приемник значений 0.38 ns/op – Chrisport

ответ

53

Обратите внимание, что the FAQ does mention consistency

Следующая последовательности. Если некоторые из методов этого типа должны иметь приемники указателей, то остальное тоже должно быть таким образом, поэтому набор методов будет соответствовать независимо от того, как этот тип используется. Подробности см. На странице section on method set.

in this thread Как уже упоминалось:

Правило об указателях против значений для приемников является то, что методы значения могут ссылаться на указатели и значения, но методы указателя могут быть вызваны только на указатели

Сейчас:

Может так meone скажите мне случай, когда приемник значений явно имеет больше смысла, чем приемник указателя?

Code Review comment может помочь:

  • Если приемник представляет собой карту, или функ чан, не используйте указатель на него.
  • Если ресивер является срезом, и метод не выполняет повторного выделения или перераспределения среза, не используйте указатель на него.
  • Если метод должен мутировать приемник, приемник должен быть указателем.
  • Если получатель представляет собой структуру, содержащую sync.Mutex или подобное поле синхронизации, приемник должен быть указателем, чтобы избежать копирования.
  • Если приемник представляет собой большую структуру или массив, приемник указателей более эффективен. Насколько велика большая? Предположим, что это эквивалентно передаче всех его элементов в качестве аргументов метода. Если это слишком велико, оно слишком велико для приемника.
  • Может ли функционировать или методы, как одновременно, так и при вызове этого метода, быть мутирующим приемник? Тип значения создает копию получателя при вызове метода, поэтому внешние обновления не будут применяться к этому ресиверу. Если изменения должны быть видны в исходном ресивере, приемник должен быть указателем.
  • Если ресивер является структурой, массивом или срезом, и любой из его элементов является указателем на то, что может быть мутирующим, предпочитайте приемник указателя, поскольку это сделает его более понятным для читателя.
  • Если приемник представляет собой небольшой массив или структуру, которая, естественно, является типом значений (например, что-то вроде типа time.Time), без изменяемых полей и без указателей, или просто простой базовый тип, такой как int или строка, приемник значений имеет смысл.
    Приемник значений может уменьшить количество мусора, которое может быть сгенерировано; если значение передается методу значения, вместо распределения в куче можно использовать копию на стеке. (Компилятор пытается быть умным, избегая этого выделения, но он не всегда может преуспеть.) Не выбирайте тип приемника значения по этой причине без предварительного профилирования.
  • И наконец, если у вас есть сомнения, используйте приемник указателя.

Часть полужирным шрифтом, найти, например, в 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) { 
... 
} 
+1

Спасибо за подробный ответ и ссылки. – Chrisport

+2

'Правило о указателях и значениях для приемников - это то, что методы значения могут быть вызваны указателями и значениями, но методы указателей могут быть вызваны только указателями. Не правда, на самом деле. Оба метода приемника приемника и указателя могут быть вызваны с помощью правильно введенного указателя или указателя. Независимо от того, какой метод вызывается, внутри тела метода идентификатор получателя ссылается на значение копии, когда используется приемник значений, и указатель, когда используется приемник указателя: См. Https: //play.golang .org/p/3WHGaAbURM –

+0

Существует большое объяснение [здесь] (https://github.com/golang/go/wiki/MethodSets#variables) «Если x адресуется, а набор методов & x содержит m, xm () является сокращением для (& x) .m(). " – tera