As far as I remember, there's a lot of machinery involved to make a C call because C doesn't know anything about Go's goroutines and their resizable stacks. So the runtime has to prepare the stack accordingly every time you make a call - and it's done on a dedicated CGO thread with a traditional stack.
That’s a misconception that cgo is slow, it’s improving and it’s not slow, when it comes to stacks, Go with its resizable stack and it’s concurrency it’s not hard to say that it’s very much Go is different from other languages. From a normal C function call that returns immediately there’s not much over head BUT when C function takes time with the runtime expecting return value, the runtime will try to align with the C stack creating a more traditional stack, I think there’s a proposal for a directive to let the compiler know there’s no need for the runtime to wait
Isn't it the opposite? cgo calls always have to switch to a larger stack (because there's no way to know in advance how much stack a function will use, and C functions don't support dynamic stack switching like Go functions do), so they have some fixed overhead per call. And that fixed overhead is most significant in relative terms when the call returns quickly.
There's a good chance you don't want the finaliser in the object; finding finalisers to run would require reading heap memory, when sweeping otherwise just touches side tables (IIUC for the bitmap allocator Go uses).
The benchmarks in the blog post are believable but the profile is not. The benchmarks do roughly the same amount of cgo work; all the extra work in the finalizer benchmark is setting up the finalizer, specifically 'runtime.addspecial'.
We ran the benchmark here and confirmed the relative times, but our profile shows runtime.addspecial as expected. The expectation is that finalizers are used rarely, for objects where the lifetime is unknown but typically long. In contrast, a finalizer benchmark is hammering on finalizers in a way that real apps basically never do.
There are also some O(N) things in finalizer registration, which turn into O(N^2) things for N finalizers for nearby allocations (only up to N=1024), again because we expect them to be very rare. Those could be fixed if this use case of tons of finalizers is realistic in practice.
If you have locally scoped C data you would typically defer C.free(p), and if you do that the cost is basically identical to calling free(p) directly.
In response to the "cgo is slow" questions, this shows that cgo calls are about 50ns on my four-year-old x86 MacBook Pro. Is that fast or slow? It depends on what the cgo call is doing. If it's executing a single add instruction, an extra 50ns is slow. If it's doing something more substantial, an extra 50ns may be nothing at all.
I haven't measured, but I find it very likely that such a neglected corner of Java behaves just like it did many years ago. Finalizers were never a widely used feature of the Java language, and are now deprecated to be fully removed in the future: https://www.infoworld.com/article/3649089/how-to-handle-java...
The post is about the cost of calling SetFinalizer.
Looking at https://github.com/golang/go/blob/master/src/runtime/mfinal.... it seems to be doing a lot, not just storing a function pointer somewhere. There are bunch of validations on the object type and the type of function that is being passed - like it should not be variadic, it should only accept one param and that first param should be of type of the object on which the finalizer is being set.
These are all reasonable things to do at compile time, however for historical reasons since go did not have generics till recently, this is probably the best they can do without compiler specific specialization (eg what was done prior for maps and slices).
“So a common instinct we all have is to say "gee, it sure would be nice if the file descriptor were closed when the File is garbage collected, let's use a finalizer". This does not work.”
and
“It is a bug to depend on the GC to run before your resources are exhausted”
and
“What are finalizers good for? Nothing generally.”
It also says “I believe I have one very small use for finalizers as they exist today, which I will talk about in a followup blog post”. I couldn’t find that post, though.
Thanks. So, the small useful feature is that it can be used to (unreliably, as finalizers aren’t guaranteed to run) detect resource leaks.
Such code wouldn’t have to mutate the object being finalized (or any other object, for that matter), so I think that use case would better be served by a more light-weight feature, removing finalizers edge cases such as objects that get revived.
> Java even plans to remove them from the language
The "Alternative techniques" section of the JEP suggests to use Cleaner.register, which was created as a replacement and has a similar API to Go's runtime.SetFinalizer.
It's absurd because OP has the wrong understanding of when to use finalizers (as mentioned many times in this thread). Finalizers are not the equivalent of C++ destructors/Rust Drop trait/etc. They're hooks for figuring out when GC is happening.
19 comments
[ 3.0 ms ] story [ 40.5 ms ] threadI've heard a couple of times that C Bindings in go are slow. Is this what is usually referred to?
So if you're writing a system in both, it's best to have the C side do a lot in each call, vs having the Go side call a lot of tiny funcs.
If you're only calling cgo in a few places, it's not too hard to check each one for memory leaks.
We ran the benchmark here and confirmed the relative times, but our profile shows runtime.addspecial as expected. The expectation is that finalizers are used rarely, for objects where the lifetime is unknown but typically long. In contrast, a finalizer benchmark is hammering on finalizers in a way that real apps basically never do.
There are also some O(N) things in finalizer registration, which turn into O(N^2) things for N finalizers for nearby allocations (only up to N=1024), again because we expect them to be very rare. Those could be fixed if this use case of tons of finalizers is realistic in practice.
If you have locally scoped C data you would typically defer C.free(p), and if you do that the cost is basically identical to calling free(p) directly.
In response to the "cgo is slow" questions, this shows that cgo calls are about 50ns on my four-year-old x86 MacBook Pro. Is that fast or slow? It depends on what the cgo call is doing. If it's executing a single add instruction, an extra 50ns is slow. If it's doing something more substantial, an extra 50ns may be nothing at all.On the contrary, the final line
> Maybe there is a way to do better, I hope there is, but I suspect not.
to me, says the writer suspects that it isn’t absurd, but as good as it can get.
I also think there’s broad agreement on the fact that finalizers almost always are a bad idea.
For golang, there’s https://crawshaw.io/blog/tragedy-of-finalizers, which says:
“So a common instinct we all have is to say "gee, it sure would be nice if the file descriptor were closed when the File is garbage collected, let's use a finalizer". This does not work.”
and
“It is a bug to depend on the GC to run before your resources are exhausted”
and
“What are finalizers good for? Nothing generally.”
It also says “I believe I have one very small use for finalizers as they exist today, which I will talk about in a followup blog post”. I couldn’t find that post, though.
Java even plans to remove them from the language (https://openjdk.org/jeps/421)
Such code wouldn’t have to mutate the object being finalized (or any other object, for that matter), so I think that use case would better be served by a more light-weight feature, removing finalizers edge cases such as objects that get revived.
The "Alternative techniques" section of the JEP suggests to use Cleaner.register, which was created as a replacement and has a similar API to Go's runtime.SetFinalizer.
https://docs.oracle.com/en/java/javase/20/docs/api/java.base...