Using C(++) in a Go application for performance

Unless a C library does a lot of work in one call I wouldn't write C code just for performance. I consider cgo to only be for interfacing with old code.

The reason is the cgo overhead. Currently function calls into cgo functions are between 50 and 100 times slower than function calls to Go code. The overhead of cgo function calls is shocking.


Cgo is fairly slow, because Go has to mess with its runtime and calling conventions in certain ways to call C functions. The only place it's really worth it is cases where the compute time significantly dwarfs this cost. It's similar to parallel, distributed, GPU and so on programming, albeit with slightly lower startup costs.

Assembly is much better, because you can write assembly that uses Go's calling convention, and is otherwise treated like native Go code, but assembly is far less portable, harder to read, and more maintenance heavy. In fact, the Go standard library writes some of the math and big packages in Plan 9-style assembly.

Gonum is an example of both of these. It uses common assembly for some functions that can be done more quickly that way, but it also leverages blas and lapack engines. It does provide a Go-blas implementation, but C-blas (which is usually ultimately Fortran-blas) is faster, and for large matrix computations almost always dwarfs the cost of leaving Go.

Generally, you want to avoid cgo when possible. Only use it when significant computation time is needed, or you need to interact with things that would be non-trivial to interact with in pure Go, such as graphics or audio drivers, or accessing common libraries like OpenCV. Even then, if you really care about performance, where possible it may be worth it to implement some sort of "call pooling" where you can schedule multiple calls from the Go side and execute them all at once with a single context switch to C.

Edit: As for C++, there are some significant issues. It can be difficult to wrap certain libraries without several layers of abstraction (since cgo cannot handle included C++ headers properly). In addition, C++ classes with destructors can't really be returned by value and have to be allocated on the heap. Since Go doesn't allow for deterministic finalization of resources, you must provide a function to explicitly free memory, and the Go user must remember to free the resource. (There is a function you may read about in the documentation called runtime.SetFinalizer but I can't say I've ever seen anyone use it, and the documentation itself comes with a bunch of caveats)

Functionality such as defer makes this more manageable, but it ruins a lot of things like RAII which make modern C++ practices safer.

Tags:

C++

C

Go

Gccgo