Crash Course on Golang Benchmarks: A Beginner's Perspective
Golang, renowned for its simplicity and efficiency, employs benchmark testing as a fundamental tool for performance evaluation. In this exploration, our focus is on the basics of Golang benchmarking, offering a concise understanding of how developers utilize this built-in testing feature to assess and optimize code performance. Let’s delve into the fundamental aspects that shape Golang benchmark tests and their significance in ensuring code efficiency.
Content
Writing a benchmark test
To understand the basics of Golang benchmarking, let’s start by writing a benchmark test for a function that generates a list of prime numbers up to N
.
import (
"math"
)
func GeneratePrimes(N int) []int {
var primes []int
for num := 2; num <= N; num++ {
if IsPrime(num) {
primes = append(primes, num)
}
}
return primes
}
func IsPrime(num int) bool {
if num < 2 {
return false
}
// Check for divisibility up to the square root of num
sqrtNum := int(math.Sqrt(float64(num)))
for i := 2; i <= sqrtNum; i++ {
if num%i == 0 {
return false
}
}
return true
}
Benchmark test will be written in the _test.go
file. We write the following benchmark test
import (
"testing"
)
func BenchmarkGeneratePrimes100(b *testing.B) {
for n := 0; n < b.N; n++ {
GeneratePrimes(100)
}
}
This will run the GeneratePrimes(100)
function b.N
times where it will generate a list of prime numbers up to 100.
Running a benchmark test
To execute the benchmark test, use the following command:
go test -benchmem -bench=. -run=^$ ./...
This command runs all benchmark tests in the current package and its subpackages, including memory statistics.
The memory statistic of the heap during benchmark test will be included with the usage of -benchmem
parameter . The -run=^$
parameter allows only test prefix with “Benchmark” to run, therefore excludes all other tests.
You will see the following output
cpu: 12th Gen Intel(R) Core(TM) i7-1255U
BenchmarkGeneratePrimes100-12 1272073 916.4 ns/op 504 B/op 6 allocs/op
PASS
We can create more benchmark test function with varying inputs and observe the difference in behavior. This can be easily done by creating a helper function:
func benchmarkPrimes(i int, b *testing.B) {
for n := 0; n < b.N; n++ {
GeneratePrimes(i)
}
}
func BenchmarkGeneratePrimes100(b *testing.B) {
benchmarkPrimes(100, b)
}
func BenchmarkGeneratePrimes1000(b *testing.B) {
benchmarkPrimes(1000, b)
}
func BenchmarkGeneratePrimes10000(b *testing.B) {
benchmarkPrimes(10000, b)
}
Let’s run the benchmark tests:
cpu: 12th Gen Intel(R) Core(TM) i7-1255U
BenchmarkGeneratePrimes100-12 1272073 916.4 ns/op 504 B/op 6 allocs/op
BenchmarkGeneratePrimes1000-12 60162 18135 ns/op 4088 B/op 9 allocs/op
BenchmarkGeneratePrimes10000-12 2900 421787 ns/op 25208 B/op 12 allocs/op
PASS
Explaining the result output of a benchmark
You will see 5 distinct columns in the result output. Let’s now break down and explain every column.
Column | Example | Description |
---|---|---|
1 | BenchmarkGeneratePrimes100-12 | The name of the benchmark test. This consist of the function name and suffix with a -XX where XX represents your GOMAXPROC. |
2 | 1272073 | Represents b.N which is the number of iterations the GeneratePrimes() function has ran. |
3 | 916.4 ns/op | The average amount of time taken to execute a single iteration. |
4 | 504 B/op | The average number of bytes allocated to the heap per iteration. |
5 | 6 allocs/op | The average number of heap allocation per iteration. |
Avoid compiler optimization
Sometimes compiler may attempt to optimize the code. In our example you can see that the return value of GeneratePrimes()
function is never used and there are no side effect within the function. Hence compiler may decides to optimize the application by removing the function entirely, since it does not affect the output of the program. This results in run time of the benchmark being artificially reduced.
To prevent compiler optimization during benchmarking, modify the benchmark tests to ensure all function outputs are used and stored. For example:
var primeList []int
func benchmarkPrimes(i int, b *testing.B) {
var generatedPrime []int
for n := 0; n < b.N; n++ {
generatedPrime = GeneratePrimes(i)
}
primeList = generatedPrime
}
Conclusion
Golang offers a straightforward yet powerful mechanism for conducting benchmarks on your functions. Leveraging the built-in testing package, developers can seamlessly assess the performance of their code, gaining valuable insights into execution times and resource utilization.
You May Also Like
When a Golang nil error is not nil
Coding in Golang are usually fun and easy for developers that are new to the language. There are sometimes gotcha which will catch out even experience …
Read ArticleAutomatic Dark Mode in Hugo Based on User System
In our previous article, we delved into how we can add dark mode using Hugo. If you missed that insightful read, it is best to catch up before …
Read Article