4 ms·
The absurd cost of finalizers in Go
- yawaramin 3y agoIt'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.
- leononame 3y agoDoes anyone know: Is there actually a better way in go? I've heard a couple of times that C Bindings in go are slow. Is this what is usually referred to?
- silisili 3y agoThe cost of calling is slow. The C code itself runs at, well, C speed. 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.
- kgeist 3y agoAs 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.
- Patrickmi 3y agoThat’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
- comex 3y agoIsn'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.
- Patrickmi 3y agoI stand corrected I don’t know much about Go’s runtime, it’s complicated that following it gives me headache
- assbuttbuttass 3y agoThe better way is to call free manually, instead of relying on a finalizer. If you're only calling cgo in a few places, it's not too hard to check each one for memory leaks.
- tedunangst 3y agoThere's no space for the finalizer in the object, so it has to be recorded on the side, and it's kinda messy.
- hayley-patton 3y agoThere'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).
- rsc 3y agoThe 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. BenchmarkAddition-16 22961580 51.77 ns/op BenchmarkAllocate-16 7611168 144.3 ns/op BenchmarkAllocateDeferFree-16 7065505 144.3 ns/op BenchmarkAllocateFinalizer-16 1028251 1243 ns/op 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.
- sieongioetnio 3y agoEffective Java 2nd Edition is 15 years old now. Is the Java information at all accurate?
- stu2010 3y agoI 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-errors-and-cleanup-without-finalize.html https://www.infoworld.com/article/3649089/how-to-handle-java...
- ankurdhama 3y agoThe post is about the cost of calling SetFinalizer. Looking at https://github.com/golang/go/blob/master/src/runtime/mfinal.go#L388 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.
- jhgg 3y agoThese 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).
- Someone 3y agoI don’t see any argument in this article for the claim of the cost being absurd. 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 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 https://openjdk.org/jeps/421)
- selimthegrim 3y agoThis is the post: https://crawshaw.io/blog/sharp-edged-finalizers https://crawshaw.io/blog/sharp-edged-finalizers
- Someone 3y agoThanks. 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.
- bkail 3y ago> 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. https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/ref/Cleaner.html https://docs.oracle.com/en/java/javase/20/docs/api/java.base...