ЗАМЕР
bench/gocontext/practice.go
Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.
- Цитируется в статье
- /ru/interview/golang/context
Запись прогона
У этого замера записи прогона нет — только скрипт.
Скрипт
142 строк//go:build ignore
// Две задачи урока про context: обе отвечают прогоном.
//
// ЗАДАЧА 1 — ПРЕДСКАЗАТЬ ВЫВОД. Четыре вещи, которые путают: отмена родителя
// доходит до потомка, но не наоборот; Err() у отменённого по таймауту и по
// вызову cancel — РАЗНЫЕ значения; Done() у context.Background() никогда не
// закрывается; и значение, положенное в потомка, невидимо родителю.
//
// ЗАДАЧА 2 — ОЦЕНИТЬ КРАТНОСТЬ. Во сколько раз дороже достать значение из
// контекста, вложенного на десять уровней, чем на один. Поиск идёт по
// цепочке — ровно как у errors.Is.
//
// ЗАПУСК:
//
// go run bench/gocontext/practice.go
package main
import (
"context"
"errors"
"fmt"
"runtime"
"testing"
"time"
)
// ---------------------------------------------------------------- задача 1
type ctxKey string
const userKey ctxKey = "user"
func task1() {
// Отмена родителя доходит до потомка.
parent, cancelParent := context.WithCancel(context.Background())
child, cancelChild := context.WithCancel(parent)
defer cancelChild()
cancelParent()
<-child.Done()
fmt.Println(errors.Is(child.Err(), context.Canceled))
// А отмена потомка родителя не трогает.
p2, cancelP2 := context.WithCancel(context.Background())
defer cancelP2()
_, cancelC2 := context.WithCancel(p2)
cancelC2()
fmt.Println(p2.Err() == nil)
// Таймаут и ручная отмена дают РАЗНЫЕ ошибки. Две строки подряд —
// намеренно: одна без другой не показывает, что они не взаимозаменяемы.
tctx, cancelT := context.WithTimeout(context.Background(), time.Millisecond)
defer cancelT()
<-tctx.Done()
fmt.Println(errors.Is(tctx.Err(), context.DeadlineExceeded))
fmt.Println(errors.Is(tctx.Err(), context.Canceled))
// Значение видно потомку, но не родителю.
base := context.Background()
withUser := context.WithValue(base, userKey, "roman")
fmt.Println(withUser.Value(userKey))
fmt.Println(base.Value(userKey))
}
// ---------------------------------------------------------------- задача 2
// ПОЧЕМУ СРАВНИВАЕТСЯ ГЛУБИНА, А НЕ «ЦЕНА WithValue».
//
// Спор про context.Value почти всегда идёт о том, «дорого ли класть». Класть
// дёшево — это одно выделение. Дорого ДОСТАВАТЬ, и цена зависит от того,
// сколько слоёв придётся пройти: Value идёт вверх по цепочке родителей, как
// errors.Is по цепочке обёрток.
const deep = 10
var (
shallow = context.WithValue(context.Background(), userKey, "roman")
nested = buildChain(deep)
sink any
)
func buildChain(n int) context.Context {
ctx := context.WithValue(context.Background(), userKey, "roman")
for i := 0; i < n; i++ {
ctx = context.WithValue(ctx, ctxKey(fmt.Sprintf("k%d", i)), i)
}
return ctx
}
func valueShallow(b *testing.B) {
for i := 0; i < b.N; i++ {
sink = shallow.Value(userKey)
}
}
func valueDeep(b *testing.B) {
for i := 0; i < b.N; i++ {
sink = nested.Value(userKey)
}
}
const rounds = 7
func nsPerOp(r testing.BenchmarkResult) float64 {
return float64(r.T.Nanoseconds()) / float64(r.N)
}
func main() {
fmt.Println("ЗАДАЧА 1 — предсказать вывод")
fmt.Println()
task1()
fmt.Println()
one, ten := 0.0, 0.0
for r := 0; r < rounds; r++ {
// Круг: оба варианта подряд, чтобы просадка машины досталась обоим.
oo := nsPerOp(testing.Benchmark(valueShallow))
tt := nsPerOp(testing.Benchmark(valueDeep))
if one == 0 || oo < one {
one = oo
}
if ten == 0 || tt < ten {
ten = tt
}
}
fmt.Println("ЗАДАЧА 2 — во сколько раз дороже Value на глубине десяти слоёв")
fmt.Println()
fmt.Printf(" Value, один слой %9.2f нс\n", one)
fmt.Printf(" Value, десять слоёв %9.2f нс\n", ten)
fmt.Printf(" кратность %9.2f\n", ten/one)
fmt.Printf(" она же округлённо %9.1f\n", ten/one)
fmt.Println()
fmt.Printf(" Лучший из %d чередующихся кругов. %s %s/%s\n",
rounds, runtime.Version(), runtime.GOOS, runtime.GOARCH)
fmt.Println()
fmt.Println(" Работа одинаковая: достать одно и то же значение. Отличается")
fmt.Println(" длина цепочки родителей — Value идёт по ней вверх, слой за")
fmt.Println(" слоем. Отсюда и правило: контекст не хранилище, и класть в")
fmt.Println(" него то, что читают в горячем пути, не стоит.")
}