Comments 14
Я вот только не очень понял, почему именно с конкурентным сборщиком мусора стало нельзя совать значения в интерфейсы «как есть»
0
В принципе, по ссылке все более-менее понятно, да. Но на первый взгляд кажется непонятным, какая связь между хранением данных в интерфейсах и конкурентным сборщиком мусора.
+1
Думаю, было бы полезнее ответить: первое слово (word в оригинале) это тип данных, а второе либо указатель, либо данные. Но поскольку атомарности чтения нет, может сложиться ситуация, когда будет прочитан тип (первое слово), но перед чтением второго слова тип поменяется, тогда может произойти, например, обращение к данным как к указателю. Чтение двух слов атомарно поддерживается не на всех платформах.
0
Спасибо за перевод и ссылки!
Возник следующий вопрос (больше даже относящийся к статье Russ Cox):
package main
import (
"testing"
)
type Stringer interface {
String() string
}
func GetString(s Stringer) string {
return s.String()
}
type Text struct {
val string
pad [24]byte
}
func (t Text) String() string {
return t.val
}
func BenchmarkGetString(b *testing.B) {
for i := 0; i < b.N; i++ {
t := Text{val: "hello"} // Moved to heap, 24 bytes for pad and 8 bytes for string.
_ = GetString(&t) // Allocation for Stringer 8 + 8 bytes.
}
}
Если запустить следующую команду:
go test iface_test.go -gcflags="-m -N -l" -bench=. -benchmem
Мы видим, среди прочего:
./iface_test.go:26: moved to heap: t
...
BenchmarkGetString-4 30000000 56.7 ns/op 48 B/op 1 allocs/op
Т.е. в каждой итерации выделяется память на heap под структуру Text
, и интерфейс Stringer
. Почему нельзя в данном случае ограничиться стеком?
+2
Привет. Я не специалист в компиляторах, но предполагаю что это возможно. Просто Go-шный escape анализатор этого не умеет пока.
Я взял и упростил твой код до такого, когда никаких строк нет. И даже return value нет. Только вызов функции интерфейса.
Если убрать вызов
из
то аллокации не будет. Предполагаю что любой такой вызов по интерфейсу приведет к escape, т.к. анализатор не знает что именно произойдет в вызываемой функции.
P.S. Чтобы получить больше подробностей, можно передать -m -m (два раза).
Чуть-чуть похожий тикет есть https://github.com/golang/go/issues/17332. Но я бы порекомендовал создать еще один с этим конкретным тест кейсом.
А искать по коду нужно по строке «receiver in indirect call»:
Я взял и упростил твой код до такого, когда никаких строк нет. И даже return value нет. Только вызов функции интерфейса.
$ cat lala_test.go
package lala
import (
"testing"
)
type Fooer interface {
Foo()
}
func GetString(s Fooer) {
s.Foo()
}
type Text struct {
val string
pad [24]byte
}
func (t Text) Foo() {
}
func BenchmarkGetString(b *testing.B) {
for i := 0; i < b.N; i++ {
t := Text{val: "hello"} // Moved to heap, 24 bytes for pad and 8 bytes for string.
GetString(&t) // Allocation for Stringer 8 + 8 bytes.
}
}
Если убрать вызов
s.Foo()
из
func GetString(s Fooer)
то аллокации не будет. Предполагаю что любой такой вызов по интерфейсу приведет к escape, т.к. анализатор не знает что именно произойдет в вызываемой функции.
P.S. Чтобы получить больше подробностей, можно передать -m -m (два раза).
Чуть-чуть похожий тикет есть https://github.com/golang/go/issues/17332. Но я бы порекомендовал создать еще один с этим конкретным тест кейсом.
А искать по коду нужно по строке «receiver in indirect call»:
marko@marko-ubuntu:~/go/src ((go1.8.1)) $ ack "receiver in indirect call"
cmd/compile/internal/gc/esc.go
1511: e.escassignSinkWhy(call, r, "receiver in indirect call")
0
Еще немного упростил пример:
package main
import (
"testing"
)
type Fooer interface {
Foo()
}
type Concrete struct {
data byte // Just to see in allocation.
}
func (c Concrete) Foo() {}
func HandleFooer(f Fooer) {}
func BenchmarkCallFooer(b *testing.B) {
for i := 0; i < b.N; i++ {
c := Concrete{} // Moved to heap.
Fooer(&c).Foo()
}
}
func BenchmarkPassFooer(b *testing.B) {
for i := 0; i < b.N; i++ {
c := Concrete{} // Not moved to heap.
HandleFooer(Fooer(&c))
}
}
Результат:
BenchmarkCallFooer-4 100000000 17.6 ns/op 1 B/op 1 allocs/op
BenchmarkPassFooer-4 500000000 2.99 ns/op 0 B/op 0 allocs/op
Да, видимо, анализатор не учитывает конкретную реализацию Foo() объекта внутри Fooer, и поэтому кладет его на heap.
Попробую создать тикет.
P.S. Спасибо за -m -m
! Не знал. =)
+1
Смотрите, если eface (interface{}), состоит из двух слов (указатель на структуру описывающую тип и указатель на данные), то в iface (в Вашем случае — Stringer) в себя ещё включает таблицу методов.
В данном случае аллокация, на данный момент неизбежна, так-как в рантайме происходят несколько вещей:
Сделать это не засунув t в кучу — довольно проблематично, насколько мне известно. (А вот если GetString будет принимать напрямую Text, а не интерфейс, то аллокаций не будет)
Другое дело, что в данном примере t — статичен и можно было произвести оптимизацию, но, я достаточно плохо разбираюсь в компиляторах, что бы оценить сложность таких оптимизаций
В данном случае аллокация, на данный момент неизбежна, так-как в рантайме происходят несколько вещей:
- Проверка на соответсвие передаваемого типа интерфейсу (наличию методов)
- Создание интерфейса
- И самое главное: вызов метода через интерфейс(переходим к объекту в памяти, от него к таблице методов и вызываем)
Сделать это не засунув t в кучу — довольно проблематично, насколько мне известно. (А вот если GetString будет принимать напрямую Text, а не интерфейс, то аллокаций не будет)
Другое дело, что в данном примере t — статичен и можно было произвести оптимизацию, но, я достаточно плохо разбираюсь в компиляторах, что бы оценить сложность таких оптимизаций
0
Это перевод habr.com/ru/post/440200, мне кажется продолжает эту тему
0
Sign up to leave a comment.
Логирование, интерфейсы и аллокации в Go