The usual library’s slices package deal contains plenty of operations that take operate arguments to regulate their habits: SortFunc, EqualFunc, and so forth. These operations are versatile, however they management their habits utilizing operate parameters, which may inhibit compiler optimizations, they usually can do plenty of copying, internally and to cross slice components to these features by worth. Because of this, they will depart 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 based mostly on the usual library implementations, however name compareRecords immediately. Because of this, the compiler has far more room to optimize.
It additionally helps specialization features that obtain tips that could components as an alternative 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 aspect sort. Some circumstances already get optimized properly 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 rationale I wrote this was to discover the efficiency influence 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 wished to see how a lot distinction specific specialization would make for some frequent Go operations.
It seems that, at the very least for a few of them, the distinction is fairly massive. I hope to see higher assist for this sort of optimization in Go over time, whether or not that’s by way of language modifications, compiler enhancements, or higher codegen tooling and a tradition that encourages writing code mills like this.

