Автор оригинала: David Wong.
Недавно, наконец, был выпущен go1.13, и одной из особенностей, на которую стоит обратить внимание, является Отсрочка повышает производительность на 30% в большинстве сценариев Однако чиновник специально не написал о том, как это продвигалось, что очень смутило людей. Поскольку я написал “глубокое понимание go defer ” и “go defer приведет к снижению производительности”, я стараюсь не использовать его? 》Такого рода статьи, поэтому мне очень интересно, какие изменения произошли, чтобы получить такой результат, поэтому сегодня я исследую эту тайну вместе с вами.
Как улучшить производительность go1.13 отложить?
1, Тестирование
1, Тестирование
$ go test -bench=. -benchmem -run=none goos: darwin goarch: amd64 pkg: github.com/EDDYCJY/awesomeDefer BenchmarkDoDefer-4 20000000 91.4 ns/op 48 B/op 1 allocs/op BenchmarkDoNotDefer-4 30000000 41.6 ns/op 48 B/op 1 allocs/op PASS ok github.com/EDDYCJY/awesomeDefer 3.234s
1, Тестирование
$ go test -bench=. -benchmem -run=none goos: darwin goarch: amd64 pkg: github.com/EDDYCJY/awesomeDefer BenchmarkDoDefer-4 15986062 74.7 ns/op 48 B/op 1 allocs/op BenchmarkDoNotDefer-4 29231842 40.3 ns/op 48 B/op 1 allocs/op PASS ok github.com/EDDYCJY/awesomeDefer 3.444s
В начале я проверил предыдущие тестовые случаи с помощью нестандартного тестового теста, и это правда, что в этих двух версиях отложить Но это не похоже на улучшение на 30%.
2, Взгляните
До (go1.12)
0x0070 00112 (main.go:6) CALL runtime.deferproc(SB)
0x0075 00117 (main.go:6) TESTL AX, AX
0x0077 00119 (main.go:6) JNE 137
0x0079 00121 (main.go:7) XCHGL AX, AX
0x007a 00122 (main.go:7) CALL runtime.deferreturn(SB)
0x007f 00127 (main.go:7) MOVQ 56(SP), BPСейчас (go1.13)
0x006e 00110 (main.go:4) MOVQ AX, (SP)
0x0072 00114 (main.go:4) CALL runtime.deferprocStack(SB)
0x0077 00119 (main.go:4) TESTL AX, AX
0x0079 00121 (main.go:4) JNE 139
0x007b 00123 (main.go:7) XCHGL AX, AX
0x007c 00124 (main.go:7) CALL runtime.deferreturn(SB)
0x0081 00129 (main.go:7) MOVQ 112(SP), BPС точки зрения сборки это выглядит как runtime.deferproc Изменено на runtime.deferprocStack. Звоните, это сделано, какая оптимизация мы С сомнением Продолжай искать.
3, Соблюдайте исходный код
_ откладывать
type _defer struct {
siz int32
siz int32 // includes both arguments and results
started bool
heap bool
sp uintptr // sp at time of defer
pc uintptr
fn *funcval
...По сравнению с предыдущими версиями, _defer В структуру в основном добавляется куча Поле для идентификации этого _defer Независимо от того, выделено ли оно в куче или в стеке, а остальные поля явно не изменены, мы можем сосредоточиться на отложить Распределение стека, посмотрите, что сделано.
Отложенный пакет
func deferprocStack(d *_defer) {
gp := getg()
if gp.m.curg != gp {
throw("defer on system stack")
}
d.started = false
d.heap = false
d.sp = getcallersp()
d.pc = getcallerpc()
*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))
return0()
}Этот блок кода довольно условен, в основном для получения вызовов отложить Указатель стека функций функции, конкретный адрес параметра входящей функции и ПК (счетчик программ) были подробно описаны в предыдущей статье “Глубокое понимание go defer”, и я не буду повторять это здесь.
Итак, этот deferprocStack Где же это особенное? Мы видим, что он помещает d.куча Установлено значение false То есть, от имени deferprocStack Метод направлен на _defer Сценариев приложений, выделенных в стеке.
отсрочка
Итак, вопрос в том, где он обрабатывает сценарии приложений, выделенные для кучи?
func newdefer(siz int32) *_defer {
...
d.heap = true
d.link = gp._defer
gp._defer = d
return d
}это новое определение Где это называется, следующим образом:
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
...
sp := getcallersp()
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc()
d := newdefer(siz)
...
}Совершенно очевидно, что была вызвана предыдущая версия. отсрочка Метод теперь используется для соответствия сценарию, выделенному для кучи.
Резюме
- Первый момент: несомненно, что
deferprocВместо того, чтобы быть удаленным, процесс оптимизируется. - Во-вторых, компилятор выберет его использование в соответствии со сценарием приложения
deferprocвсе ещеМетоды deferprocStack, которые предназначены для сценариев использования, выделенных в куче и стеке соответственно.
4, Как выбрать компилятор
esc
// src/cmd/compile/internal/gc/esc.go
case ODEFER:
if e.loopdepth == 1 { // top level
n.Esc = EscNever // force stack allocation of defer record (see ssa.go)
break
}SSA
// src/cmd/compile/internal/gc/ssa.go
case ODEFER:
d := callDefer
if n.Esc == EscNever {
d = callDeferStack
}
s.call(n.Left, d)Резюме
С точки зрения этой комбинации, ядром является e.глубина петли Будут показаны результаты анализа escape Будут показаны результаты анализа escape Установить в Эскревер То есть _defer , назначенный стеку, этот e.глубина цикла
// src/cmd/compile/internal/gc/esc.go
type NodeEscState struct {
Curfn *Node
Flowsrc []EscStep
Retval Nodes
Loopdepth int32
Level Level
Walkgen uint32
Maxextraloopdepth int32
}Вот ключ Глубина цикла В настоящее время он имеет три идентификатора значений, а именно:
- -1: В целом.
- 0: возвращаемая переменная.
- 1: Возрастающее значение функции верхнего уровня или внутренней функции.
Это немного странно читать в сочетании с вышесказанным e.глубина петли Другими словами, когда отложить функцию , когда это функция верхнего уровня, она будет выделена стеку. Но если отложить функцию
func main() {
for p := 0; p < 10; p++ {
defer func() {
for i := 0; i < 20; i++ {
log.Println("EDDYCJY")
}
}()
}
}Для просмотра сборки:
$ go tool compile -S main.go
"".main STEXT size=122 args=0x0 locals=0x20
0x0000 00000 (main.go:15) TEXT "".main(SB), ABIInternal, $32-0
...
0x0048 00072 (main.go:17) CALL runtime.deferproc(SB)
0x004d 00077 (main.go:17) TESTL AX, AX
0x004f 00079 (main.go:17) JNE 83
0x0051 00081 (main.go:17) JMP 33
0x0053 00083 (main.go:17) XCHGL AX, AX
0x0054 00084 (main.go:17) CALL runtime.deferreturn(SB)
...Очевидно, что в конце концов отложим Метод runtime.deferproc , то есть он выделяется в кучу, без проблем. Для неявной итерации вы можете использовать goto Утверждение для достижения этой функции, а затем их собственная проверка, здесь повторяться не будет.
резюме
Согласно результатам анализа, производительность go1.13 defer улучшена на 30%, что в основном связано с изменением правил распределения стека отложенных объектов defer Из для-цикла Анализ глубины итерации, если глубина цикла Если оно равно 1, результат анализа экранирования устанавливается и выделяется в стек, в противном случае он выделяется в кучу.
Действительно, я лично чувствую, что для большинства сценариев есть много улучшений, и жалуюсь на некоторых людей. отложить Проблема “плохой” производительности. Кроме того, я думаю, что с go1.13 вам также нужно немного знать о его механизме. Не делайте дикую вложенную итерацию откладывать Возможно, вы не сможете добиться максимальной эффективности.
Если вы хотите узнать больше деталей, вы можете взглянуть отложить Представленное содержание этой части также включено в официальные тестовые примеры.
Оригинал: “https://developpaper.com/how-is-the-performance-of-go1-13-defer-improved/”