Build times

The Go compiler essentially transforms every instance of a generic into a defined, named type, and that additional work must impact build times, right?

The example

This page describes how to run BenchmarkGoBuild, a Go-based benchmark that benchmarks how long it takes to build:

  • a package archive
  • a binary executable

Each build varies based on the:

  • list type, ex. boxed, generic, typed
  • number of distinct type definitions

The packages used by this benchmark are:

  • ./lists/boxed: defines type List []interface{}
  • ./lists/typed: defines zero to many list types based on build tags
  • ./lists/generic: defines type List[T any] []T

The typed and generic packages are also subject to the following build tags:

  • int: activates that package’s list of int
  • int8: activates that package’s list of int8
  • int16: activates that package’s list of int16
  • int32: activates that package’s list of int32
  • int64: activates that package’s list of int64

The benchmark

Run the benchmark with the following command:

  1. docker run -it --rm go-generics-the-hard-way \
  2. go test -bench GoBuild -run GoBuild -count 1 -v ./06-benchmarks

The following table was generated by piping the above command to hack/b2md.py -t buildtime:

Artifact type Number of types Ops - typed Ops - generic Increase (ops) Increase (%) ns/op - typed ns/op - generic Increase (ns/op) Increase (%)
pkg 0 30 31 1 3.33 38920897 36911568 -2009329 -5.16
1 27 28 1 3.7 41662250 39276738 -2385512 -5.73
2 28 30 2 7.14 40618555 41801543 1182988 2.91
3 30 30 0 0 39767116 39966252 199136 0.5
4 30 27 -3 -10 39445407 42310255 2864848 7.26
5 28 27 -1 -3.57 39945665 42252446 2306781 5.77
bin 0 1 1 0 0 1575856128 1621482710 45626582 2.9
1 1 1 0 0 1588863799 1572409429 -16454370 -1.04
2 1 1 0 0 1690833210 1643376398 -47456812 -2.81
3 1 1 0 0 1650099076 1602027340 -48071736 -2.91
4 1 1 0 0 1720921554 1604378688 -116542866 -6.77
5 1 1 0 0 1631208265 1601904971 -29303294 -1.8

Validating the artifacts

To validate each of the produced artifacts contain the expected types, use the following commands:

  1. # Print types in each generic package archive
  2. find -s ./06-benchmarks -name "generic-*-types.a" -type f -print0 | \
  3. xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'go.shape.int[[:digit:]_]\{1,\}' | sort -ru && echo"
  1. # Print types in each generic binary executable
  2. find -s ./06-benchmarks -name "generic-*-types.bin" -type f -print0 | \
  3. xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'int[[:digit:]]\{0,\}List' | sort -ru && echo"
  1. # Print types in each typed package archive
  2. find -s ./06-benchmarks -name "typed-*-types.a" -type f -print0 | \
  3. xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'Int[[:digit:]]\{0,\}List' | sort -ru && echo"
  1. # Print types in each typed binary executable
  2. find -s ./06-benchmarks -name "typed-*-types.bin" -type f -print0 | \
  3. xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'int[[:digit:]]\{0,\}List' | sort -ru && echo"

In all cases the output should indicate a file with:

  • 0 types has no match
  • 1 type matches int
  • 2 types match int and int8
  • 3 types match int, int8, and int16
  • 4 types match int, int8, int16, and int32
  • 5 types match int, int8, int16, int32, and int64

Key takeaways

The Go compiler appears to be incredibly efficient at stencling the generic types together into concrete types as the build times for the generic lists do not demonstrate any distinguishable difference from the typed lists.


Next: File sizes