Бенчмарк-тестирование в Go
Мне очень нравится, что стандартная библиотека Go включает в себя так много вариантов тестирования прямо из коробки. В этом посте мы представим вариант тестирования производительности в Go.
Как и во многих случаях в Go, при использовании этой функции необходимо следовать некоторым соглашениям Benchmark
. Они очень похожи на те, которые используются для написания модульных тестов.
Сначала тесты Benchmark
помещаются в файлы с _test.go
суффиксом. Следующее правило заключается в том, что тест производительности использует префикс Benchmark
вместо Test
, поэтому функция выглядит следующим образом:
func BenchmarkXxx(*testing.B) {
Для запуска тестов производительности вы используете обычную команду test
, но с дополнительным флагом:
go test -bench=.
Имея эту информацию, мы готовы написать несколько тестов. Пример кода, который мы попытаемся протестировать, представляет собой два алгоритма сортировки BubbleSort
и QuickSort
.
func BubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func QuickSort(arr []int) {
quickSort(arr, 0, len(arr)-1)
}
func quickSort(arr []int, low, high int) {
if low < high {
pivot := partition(arr, low, high)
quickSort(arr, low, pivot-1)
quickSort(arr, pivot+1, high)
}
}
func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low - 1
for j := low; j < high; j++ {
if arr[j] < pivot {
i++
arr[i], arr[j] = arr[j], arr[i]
}
}
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
}
Мы создаем новый файл sorting_benchmark_test.go
и добавим вспомогательный код для тестов производительности.
func makeRandomNumberSlice(n int) []int {
numbers := make([]int, n)
for i := range n {
numbers[i] = rand.Intn(n)
}
return numbers
}
const LENGTH = 10_000
Эта функция makeRandomNumberSlice
будет использоваться для создания срезов, которые можно будет отсортировать в тестах. Переменная LENGTH
представляет длину срезов, которые мы собираемся отсортировать.
Теперь давайте добавим два эталонных теста, по одному для каждого алгоритма сортировки.
func BenchmarkBubbleSort(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
numbers := makeRandomNumberSlice(LENGTH)
b.StartTimer()
sorting.BubbleSort(numbers)
}
}
func BenchmarkQuickSort(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
numbers := makeRandomNumberSlice(LENGTH)
b.StartTimer()
sorting.QuickSort(numbers)
}
}
Структура testing.B
предоставляет поле с именем N
, которое необходимо для того, чтобы инструмент мог выполнять правильные измерения. Это из документации Go.
Тестовая функция должна запустить целевой код b.N раз. Во время выполнения теста, b.N корректируется до тех пор, пока функция теста не будет длиться достаточно долго, чтобы можно было надежно синхронизировать его.
Оба теста используют один и тот же шаблон. Для каждого цикла мы создаем новый случайный фрагмент длины LENGTH
, а затем сортируем его. Мы также следим за тем, чтобы таймер не работал во время создания среза, чтобы он не включался в измерение.
При запуске с помощью, go test -bench=.
результат будет выглядеть примерно так:
Вывод начинается с сообщения нам, на какой машине выполняется тест. Затем есть две строки, представляющие каждый написанный нами тест производительности.
Суффикс -16
в первом столбце (BenchmarkBubbleSort-16
/BenchmarkQuickSort-16
) сообщает нам, сколько процессоров используется для запуска тестов. Во втором столбце указано, сколько петель было использовано во время теста, b.N
. В последнем столбце указана скорость для каждого цикла.
Из этого теста мы можем ясно увидеть, кто победитель QuickSort
. Быстрая сортировка имеет среднюю временную сложность O(n log n), а BubbleSort
имеет временную сложность для наихудшего и среднего случая O(n^2).
Как видите, начать бенчмарк-тестирование в Go несложно, сложности заключаются в том, что и как тестировать код. Конечно, при написании тестов производительности в Go есть и другие флаги и функции, которые можно изучить и использовать.
Удачного бенчмаркинга!