Запустим бенчмарки в cmd/strconv_vs_fmt
:
go test -bench=.
Что наблюдаем и почему?
Соберем информацию по выделению памяти:
go test -bench=. -benchmem
Теперь соберем профиль CPU:
go test -bench=. -cpuprofile cpu.out
И запустим pprof для его визуализации (документация: https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph):
go tool pprof cpu.out
Как объяснить полученный результат в контексте бенчмарок?
Соберем профиль памяти:
go test -bench=. -memprofile mem.out
И запустим pprof для его визуализации:
go tool pprof mem.out
Что наблюдаем?
Объяснение устройства интерфейсов: см. слайды.
Убедимся в том, что FormatIntSprintf
действительно аллоцирует память при передаче i
в fmt.Sprintf
(это также можно сделать при помощи команды list
pprof: list optimizations/cmd/strconv_vs_fmt.FormatIntSprintf
):
go build -gcflags '-S -N' main.go &> main.s
Определение convT64
находится в исходном коде Go в src/runtime/iface.go
.
Вопрос: разобрав как устроены интерфейсы в Go, посмотрите на 9 строку файла cmd/iface_puzzle/decl.go
. Как вы думаете, что она делает?
Запустим бенчмарки с дополнительным флагом:
go test -gcflags '-m' -bench=.
Разница между стеком и кучей: см. слайды.
См. пример в cmd/map_string_keys
.
Как изменятся измерения, если поменять тип ключа в мапе на int
?
См. определение структуры string
в исходниках Go в src/runtime/string.go
.
Перед запуском бенчмарок посмотрите на код в cmd/slices_cap/main.go
и ответьте:
- какая из версий будет работать быстрее?
- будет ли у них одинаковое потребление памяти?
Запустим бенчмарки в cmd/slices_cap
:
go test -bench=. -benchmem
Разберем устройство слайсов: см. слайды и src/runtime/slice.go
.
Понаблюдаем как меняется емкость слайсов:
go test ./... -run=TestPrintSliceCapacityChanges -v
Попробуем запустить тест TestSumFirstElementsOfSlice
из cmd/bce
:
go test ./... -bench=this-bench-does-not-exist -run=TestSumFirstElementsOfSlice
Теперь отключим bounds checking:
go test -bench=this-bench-does-not-exist -gcflags='-B' ./... -run=TestSumFirstElementsOfSlice
Что наблюдаем?
Посмотрим на разницу в ассемблерном коде:
go build -gcflags '-S -N' main.go &> main.s
go build -gcflags '-S -N -B' main.go &> main-no-bc.s
Проверим, что версия без проверок действительно быстрее:
rm ./*.s
go test -run=this-test-does-not-exist -bench="^BenchmarkSumFirstElementsOfSlice$"
go test -run=this-test-does-not-exist -gcflags="-B" -bench="^BenchmarkSumFirstElementsOfSlice$"
Go позволяет узнать, на какой строке происходит bound check:
go test -gcflags '-d=ssa/check_bce/debug=1' -run=this-test-does-not-exist -bench="^BenchmarkSumFirstElementsOfSlice$"
Как мы видим bound check происходит внутри цикла. Можно ли это оптимизировать?
Запустим бенчмарки для новой функции:
go test -gcflags="-d=ssa/check_bce/debug=1" -bench="^BenchmarkSumFirstElementsOfSliceBCE$" -run=this-test-does-not-exist -count=5
Посмотрите на код новой функции в main_bce.go
.
Документация к sync.Pool
находится здесь: https://pkg.go.dev/sync#Pool
Код примера находится в sync-pool
.
Запустим пример без использования sync.Pool
:
go test -run TestNoPool -memprofile mem.out -v -count=2
и с sync.Pool
:
go test -run TestWithPool -memprofile mem-pool.out -v -count=2
https://habr.com/en/companies/yandex_praktikum/articles/729570/