Benchmarking and Profiling

Hey! If you love Go and building Go apps as much as I do, let's connect on Twitter or LinkedIn. I talk about this stuff all the time!

Want to learn how to build better Go applications faster and easier? You can.

Check out my course on the Go Standard Library. You can check it out now for free.


Understanding Performance in Go: Benchmarking and Profiling

Introduction

As you start writing more complex programs in Go, making sure they run efficiently becomes crucial. This is where benchmarking and profiling come in – these are powerful tools that allow you to measure the performance of your code and identify bottlenecks.

What is Benchmarking?

Benchmarking is like a race for your code. You write a test that runs a specific piece of code multiple times, measuring how long it takes to complete each run. This helps you understand how fast your code performs in a controlled environment.

Think of it like this: if you have two functions, functionA() and functionB() that both do the same thing, but one is faster than the other, how can you tell which function is better? You can’t just look at the code and say “this looks fast!” You need to benchmark them!

Function vs. Code:

Think of a function as a single task or a small part of your program. A large program might have many tasks (functions) that all contribute to its overall performance. While you can make educated guesses about code efficiency, the only way to be sure is to measure it in action.

That’s where benchmarking comes in. It helps you compare different versions of a function (or even entire programs) and see which one performs better.

Benchmarking Tools in Go

Go provides excellent built-in tools for benchmarking. To write a benchmark, you’ll typically use the testing package. This package allows you to write functions that will measure your code’s performance.

The testing/quick Package:

  • Provides random testing: The testing/quick package helps us test how our code behaves with different input values. This is great for finding edge cases and potential errors in our logic.
  • Uses properties to define correctness: Instead of testing specific inputs, we can define a “property” (a relationship that should hold true for all inputs) and use it to generate a suite of random tests.

Best Practices:

  • Benchmark small, focused functions: For better readability and understanding of the code’s performance, isolate individual tasks in your code by creating separate benchmark functions.
  • Use realistic data:

Don’t just test how fast your function runs with simple inputs. Benchmarking needs to use a variety of data to show how it performs under different conditions.

  • Measure multiple times: Run each benchmark several times and compare the results. This helps account for variability in execution time.
  • Analyze the output:

Use the testing package’s built-in functions or tools like go test -benchmem to analyze memory usage.

  • Benchmarking different parts of a program:

The key is to choose inputs that accurately reflect the data your function will typically handle. For example, if you are writing a benchmark for a function that sorts lists, it’s important to consider:
* Small lists:

How does the function perform when sorting a small number of items?

  • Large lists:

What about when there are a lot of items to sort? Does your function handle this well?

Writing Efficient and Readable Benchmarking Code

You can write faster, more effective benchmarks by:

  • Using realistic inputs
  • Focusing on the specific code you want to test (e.g., benchmark sorting algorithms with different input sizes)

When to use testing vs. benchmarking with a focus on efficiency

The testing package in Go is designed for testing individual functions and ensuring they produce the expected results.

For example, if you have a sumNumbers() function that takes two numbers as input and returns their sum, you could write a benchmark function to analyze its performance with lists of varying sizes:

func BenchmarkSumNumbers(b *testing.B) {
  // Define the suite for the benchmark function
  for n := 0; n < b.N; n++ {
    // ... your code to define the 'sumNumbers' function ...
  }

This code snippet demonstrates how to use the testing/testing package in Go.

import "testing"

func BenchmarkSumNumbers(b *testing.T) {
  numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // Example list
  for i := 0; i < len(numbers); i++ {
    b.Logf("Line %d: %s", i, b.Errorf("Failed to sum numbers"))
  }

  // Define a function for the benchmark
  func (b *testing.B) calculateSum() int {
    // ... your code to sum the numbers in the `numbers` list ...
  }

  // Benchmarking with 'go test'
  for n := 0; n < b.N; n++ {
      calculateSum()
  }
  // Benchmarking the 'sumNumbers' function:
  // ...

  // BenchmarkSum(b *testing.B)
  benchmarks := []int{10, 100, 1000, 10000, 100000} // Example list sizes
  for _, size := range sizes {
    b.N = size // Set the number of iterations for each benchmark run

Conclusion

While go test is a great tool for testing individual functions, Go’s built-in profiling tools can be used to analyze specific code blocks and pinpoint areas where your code might be slow.

Remember, the examples above are just basic illustrations. You need to understand how the ‘testing/testing’ package works in order to use it effectively. The key takeaway is that you should focus on writing efficient algorithms for benchmarking, as this will give you a clearer picture of your program’s performance in different scenarios.

This code snippet demonstrates how to test a function and its potential optimizations. By using the testing/testing package, you can measure the performance of each ‘size’ and compare them against other implementations.

  • Benchmarking individual functions:
// Benchmark your 'func' to see how it performs with different input sizes
func BenchmarkSum(b *testing.B) {
  for i := 0; i < b.N; i++ {
    sum := 0
    for _, num := range numbers {
      b.Run("singleFunction"+strconv.FormatInt(i, 10), func(s *testing.S) {
        // ... your code to sum the numbers in the 'numbers' list ...
        for n := 0; n < b.Size(); n++ {
          for _, s := range numbers {
            sum += s

* 'running' a loop' function for testing:

```go
func calculateSum(b *testing.B) {
  // ... create the 'numbers' list with different values for 's' (e.g., 10, 100, 1000)
    for n := 0; n < numbers[i] ; i++ {
      // Your original 'numbers' function

The `testing/b` package allows you to measure the performance of your code.

* **Benchmarking with different inputs:**

```go
func calculateSum(size int,  *testing.T) {
    // Use the 'numbers' slice for the benchmark
    var sum int
    for i := 0; i < size; i++ {
      for j := 0; j < size; j++ {
        b.Logf("Iteration %d: Sum of %d numbers", s, s*10)

* **Profiling the 'numbers' function:**
```go
func


Stay up to date on the latest in Coding for AI and Data Science

Intuit Mailchimp