seebs / affine

trivial partial Go implementation of affine matrices because of an interesting benchmark

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Affine Benchmark

This is a trivial benchmark that came into existence because I was thinking about the API for doing this, and wanted to know about performance of several options.

Results:

Old version (obsolete-ish):

AffineTypeSwitch-8     10000  141219 ns/op       0 B/op      0 allocs/op
AffineInterface-8       2000  785927 ns/op  240001 B/op  30000 allocs/op
AffineTypeSwitchPre-8  10000  154993 ns/op       0 B/op      0 allocs/op
AffineInterfacePre-8    5000  317816 ns/op       0 B/op      0 allocs/op
AffineConcrete-8      200000   10532 ns/op       0 B/op      0 allocs/op

In the old version, I didn't realize at the time that the concrete implementation was benefitting in part from being inlined. I also concluded that the benchmark of the interface allocation was not very interesting, so I dropped that, and added new tests:

BenchmarkAffineConcreteInline-8           300000              5709 ns/op
BenchmarkAffineConcrete-8                  10000            113512 ns/op
BenchmarkAffineTypeSwitchHand-8            10000            125817 ns/op
BenchmarkAffineTypeSwitchInlined-8         10000            129791 ns/op
BenchmarkAffineTypeSwitchCall-8            10000            228741 ns/op
BenchmarkAffineInterface-8                  5000            298248 ns/op

Note that there's multiple calls through interfaces happening here, and they're happening several times per loop, on a loop of 10,000 items, so the actual interface-overhead appears to be about 1-1.5ns per call, give or take. This is similar to things I've seen elsewhere, except that at least one test (a derivative of Egon Elbre's interface benchmark, found at https://github.com/egonelbre/exp/tree/master/bench/iface) I ran showed zero overhead for an interface compared to direct function calls!

The surprising big difference here is that the performance gap between calls to a type-switched function and calls through an interface dropped significantly when switching from passing a 6-item struct to passing a pointer to it, so probably a large portion of what I was seeing originally was actually just the cost of passing parameters around.

To compare, all of these are performing essentially the same math operations. What changes is how those get called:

ConcreteInline: Using inlineable functions, make direct calls
on concrete types.

Concrete: Using `go:noinline` functions, make direct calls
on concrete types.

TypeSwitchHand: Make a call on an interface-typed thing, which
uses a type switch to directly inline the code.

TypeSwitchInlined: Use a type switch to call inlineable functions
on concrete types.

TypeSwitchCall: Use a type switch to call `go:noinline` functions
on concrete types.

Interface: Use the interface methods.

About

trivial partial Go implementation of affine matrices because of an interesting benchmark


Languages

Language:Go 100.0%