The usual library’s slices bundle consists of a lot of operations that take perform arguments to manage their conduct: SortFunc, EqualFunc, and many others. These operations are versatile, however they management their conduct utilizing perform parameters, which may inhibit compiler optimizations, they usually can do lots of copying, internally and to cross slice parts to these features by worth. In consequence, they will go away a lot of efficiency on the desk.
To resolve this, I wrote monoslices: a instrument that generates specialised (“monomorphic”) variations of the slices.*Func operations.
monoslices is just not a library. It generates code that will get checked into your mission and provides no module dependencies. The generator itself is meant to be put in as a instrument dependency in your mission, which helps with versioning and toolchain consistency:
go get -tool github.com/zolstein/monoslices
Then, it’s invoked with:
go instrument monoslices
from the CLI, or by way of go generate.
As an alternative of writing:
slices.SortFunc(data, compareRecords)
you declare compareRecords because the comparator for a generated set of operations:
func compareRecords(a, b Report) int { … }
//monoslices:generate identify=Information cmp=compareRecords
and monoslices generates features like:
func RecordsSort(s []Report)
func RecordsBinarySearch(s []Report, goal Report) (int, bool)
func RecordsMin(s []Report) Report
These generated features are primarily based on the usual library implementations, however name compareRecords instantly. In consequence, the compiler has rather more room to optimize.
It additionally helps specialization features that obtain tips to parts as a substitute of copies, utilizing the byref possibility:
func compareRecords(a, b *Report) int { … }
//monoslices:generate identify=Information cmp=compareRecords byref=true
How a lot this issues relies upon closely on the operation and ingredient sort. Some circumstances already get optimized effectively and there’s mainly no distinction. However for a lot of operations, the distinction is substantial, and byref advantages even reasonably sized values:
| Operation | 16 B worth | 64 B by-ref | 256 B by-ref |
|---|---|---|---|
Equal |
1.6-2.0× speedup | 3.3-5.9× speedup | 3.4-10× speedup |
BinarySearch |
1.7-1.9× speedup | 1.8-2.3× speedup | 2.9-3.8× speedup |
Kind |
1.7-2.1× speedup | 3.3-4.0× speedup | 4.2-5.3× speedup |
A part of the explanation I wrote this was to discover the efficiency impression of specialization in Go. Specialization is a crucial a part of how languages like Rust and C++ generate environment friendly generic code. Generic Go code can’t focus on all the identical methods, and I needed to see how a lot distinction express specialization would make for some frequent Go operations.
It seems that, no less than for a few of them, the distinction is fairly giant. I hope to see higher help for this type of optimization in Go over time, whether or not that’s by language adjustments, compiler enhancements, or higher codegen tooling and a tradition that encourages writing code turbines like this.

