Рубрики
Uncategorized

Как улучшается производительность go1.13 defer?

Автор оригинала: 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/”