Batching calls across cgo
I wrapped a C checksum function and called it once per 32-byte record. The C loop won its isolated benchmark, yet the complete program lost. I had measured the work and ignored the doorway.
The fix was not exotic:
// One crossing for the whole buffer.
sum := C.checksum((*C.uchar)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
I benchmarked batches of 1, 16, 256, and 4096 records from the same input. On my machine the one-record version managed about 7 MB/s; batches of 16 reached 58 MB/s, 256 reached 170 MB/s, and 4096 changed little at 174 MB/s. These are not portable constants. The flattening was the useful result: by 256 records, the checksum was work again and the bridge was no longer the benchmark.
I also timed the pure Go checksum in the same harness. It reached 112 MB/s. That made the decision less silly: either stay in Go and keep the simple boundary, or batch enough for C’s faster loop to pay its admission fee. Calling C per record was the one clearly bad option.
The batch API takes a contiguous buffer plus record metadata and returns all checksums at once. It also makes ownership easy to state: the buffer remains valid during the call, and C retains no Go pointer afterward.
The caveat is the empty slice: &buf[0] is invalid when len(buf) == 0, so real code checks length first. C memory needs C lifetime rules, callbacks need even more care, and a blocking C call affects runtime scheduling differently from an ordinary Go function.
I picked 256 records because larger batches bought almost nothing and delayed results. The benchmark supplied both halves of that choice; “fewer cgo calls” by itself would have pushed the batch size toward absurdity.