Ловушки бенчмарков
Первый бенчмарк показал «улучшилось в десять раз», и Миша понёс цифры на ревью к Ане. Там они рассыпались: компилятор просто выбросил измеряемый код, и замерялась пустота.
бенчмарк легко замеряет пустоту: компилятор выбрасывает неиспользуемый результат, а подготовка в цикле попадает в замер — от первого спасает sink-переменная, от второго ResetTimer.
Компилятор выбрасывает результат
var sink bool
func BenchmarkValidateName(b *testing.B) {
var ok bool
for i := 0; i < b.N; i++ {
ok = validateName(name)
}
sink = ok
}
Если результат функции не используется, оптимизатор может выбросить вызов целиком, и бенчмарк замерит пустоту. Лекарство — складывать результат в переменную уровня пакета (sink), которую компилятор не может выкинуть.
Подготовка внутри цикла
func BenchmarkFileLookup(b *testing.B) {
byID := buildIndex() // подготовка — вне замера
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = byID[id]
}
}
Всё, что стоит в теле цикла, попадает в замер. b.ResetTimer обнуляет счётчик перед циклом; есть и пара b.StopTimer / b.StartTimer для мест, где замер нужно приостановить.
Читать результат, а не верить
Бенчмарк легко соврать самому себе: слишком мало итераций, шумная машина, забытый ResetTimer. Привычки: сравнивать на одной машине, гонять с -count несколько раз, смотреть на allocs/op, а не только на ns/op, и проверять, что замеряешь то, что думаешь (например, через -benchtime=10s).
Вы узнаете:
- как
b.ReportAllocs()показывает allocs/op без-benchmem - как
b.Runделает суб-бенчмарки
Проверьте себя
Результат функции в бенчмарке не используется. Что замеряет бенчмарк?
Подготовка (buildIndex) внутри цикла бенчмарка. Что с замером?
Единичный прогон бенчмарка — основание для вывода?
правильных ответов
Ловушки: выброшенный компилятором код (sink), подготовка в цикле (ResetTimer), вера единственному прогону. Бенчмарк — инструмент честного сравнения, а не магии.
10 000 ₽ · 60 минут · онлайн