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: an exemplar case
- The benchmark: how does it stack up?
- Key takeaways: what it all means
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
: definestype List []interface{}
./lists/typed
: defines zero to many list types based on build tags./lists/generic
: definestype List[T any] []T
The typed
and generic
packages are also subject to the following build tags:
int
: activates that package’s list ofint
int8
: activates that package’s list ofint8
int16
: activates that package’s list ofint16
int32
: activates that package’s list ofint32
int64
: activates that package’s list ofint64
The benchmark
Run the benchmark with the following command:
docker run -it --rm go-generics-the-hard-way \
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:
# Print types in each generic package archive
find -s ./06-benchmarks -name "generic-*-types.a" -type f -print0 | \
xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'go.shape.int[[:digit:]_]\{1,\}' | sort -ru && echo"
# Print types in each generic binary executable
find -s ./06-benchmarks -name "generic-*-types.bin" -type f -print0 | \
xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'int[[:digit:]]\{0,\}List' | sort -ru && echo"
# Print types in each typed package archive
find -s ./06-benchmarks -name "typed-*-types.a" -type f -print0 | \
xargs -0 -I% sh -c "echo % && go tool objdump -S % | grep -o 'Int[[:digit:]]\{0,\}List' | sort -ru && echo"
# Print types in each typed binary executable
find -s ./06-benchmarks -name "typed-*-types.bin" -type f -print0 | \
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
andint8
- 3 types match
int
,int8
, andint16
- 4 types match
int
,int8
,int16
, andint32
- 5 types match
int
,int8
,int16
,int32
, andint64
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