6 ms·
That was a great read. All that linker wrangling is sure to break on the next version of go/rust/linker isn't it? I wonder if it would have been easier to disa
by nickcw 2y ago
That was a great read. All that linker wrangling is sure to break on the next version of go/rust/linker isn't it?
I wonder if it would have been easier to disassemble the rust binary and turn it into Go assembly and use that.
That would need a fairly complicated program to process the binary back into assembler. Maybe getting the rust compiler to output assembly and processing into Go assembly would be the way.
Using Go assembly would save fighting with the linker, be more likely to survive upgrades and it would be cross platform (well at least on platforms with the same CPU arch).
- neonsunset 2y agoImagine picking a language with terrible FFI overhead and weak compiler only to fight these two worsts aspects of it in an attempt to fix them. C# with zero-cost FFI, none of the performance penalty and ability to statically link everything together is a strictly better choice. Saner type system and syntax too.
- kgeist 2y agoC#'s FFI is kind of zero-cost with blittable types (int, float). You still need to do marshalling for anything more complex (strings was a common issue on Linux, UTF8<=>UTF16), also memory pinning. Last time I checked, it also notifies the GC the thread entered native code (can't be preempted, the stack needs to be scanned conservatively etc.) After exiting a native function IIRC there's a safepoint check. I remember years ago it was common knowledge that P/Invoke is not suitable for calling hot functions in a loop, you had to create native helper functions which make all the calls in one go. Maybe it has changed?
- neonsunset 2y agoMemory pinning is practically free and GC does quite a bit of work to further minimize its impact on throughput, common practice is to use stack buffers or natively allocated memory for marhsalled or other data anyway (remember - free FFI, so you can always do malloc and free which marshallers do). In practice UTF-16<->UTF-8 conversion rarely shows up on flamegraph as it turns out built-in transcoding is very fast and does not leave dangling data that GC needs to clean up. It is also not as frequently needed - you can just get a byte* for free out of "hello, world"u8 and pass it without any extra operations (the binding generator will do that for you). On top of that, complex blittable structures are first and foremost expressible in C# - you can easily have C binary tree, or an array of arrays with byte**. Performance-sensitive code that does interop heavily exploits that to give actual C-like experience. Also short-lived FFI calls do not need to notify GC (which is also cheap it toggles a boolean), libraries that care about extra last nanosecond annotate them with `[SuppressGCTransition]` which further streamlines assembly around FFI call. For performing FFI in a loop - the cost comes from the same reason a non-inlineable cross-compilation-unit calls are expensive within C++. Because we're talking plain call level of overhead, which is by definition as cheap as FFI gets. Of course that can still be a bottleneck if you have a small function beyond FFI boundary within a hot loop. In that case you might as well port it to C# to make it inlineable which is going to be faster, or have an FFI call for a batched operation. Try `dotnet publish -o . -p:StripSymbols=false`ing this: https://github.com/U8String/U8String/tree/main/Examples/Interop/CSharp https://github.com/U8String/U8String/tree/main/Examples/Inte... and then disassembling it with Ghidra. You will be positively impressed with how codegen looks - there will be simple direct calls into Rust with single bool checks for a potential GC poll after them (and compiler will merge those too after multiple consecutive calls).
- aatd86 2y agoSo it's actually about the same situation in Go from what I can understand.
- neonsunset 2y agoNo, Go FFI is so slow it makes Python look fast which has great FFI performance, just being an interpreted language hurts. It needs to perform stack switching and worker thread pinning, and for some reason even that is slow. I don't know why. Note on tinygo as I've been put in the jail heh: Realistically no one runs TinyGo in production as in back-end workloads or larger user-facing applications. And when you do run it, at most you match pre-existing FFI performance of .NET which ranges at 0.5-2ns at throughput (which I assume how you are testing it) depending on flags and codegen around specific arguments. Up to 50 times difference with a standard Go, that is a lot, isn't it? All custom runtime flavours in Go usually come with significant performance issues or other tradeoffs. Something I never need to deal with when I solve the same problems with .NET, maybe occasionally addressing compatibility with AOT for libraries that have not been updated if it's a desired deployment mode.
- randomdata 2y ago> No, Go FFI is so slow it makes Python look fast Which Python and which Go? There are so many different implementations and different versions of those implementations that this broad statement is meaningless. From what I have installed on my machine, CPython 3.11.6 seems to take around 80ns to call a C function, gc 1.22.0 takes around 50ns to call the same C function, and tinygo 0.32.0 only takes around 1ns! Such benchmarking is always fraught with problems, so your milage may vary, but on my machine under this particular test Go wins in both cases, and tinygo is doing so at about the same speed as C itself.
- darby_nine 2y ago> CPython 3.11.6 seems to take around 80ns to call a C function, gc 1.22.0 takes around 50ns to call the same C function How does this make any sense? C shares a runtime with CPython. What is it doing where it manages to be slower than go?
- fingerlocks 2y agoThis is _hacker_ news, doing the thing it wasn’t designed to do is the point. The harder the challenge, the better. Yes there are tons of better choices for rust interop. Any LLVM language will work. That’s not interesting.
- FiloSottile 2y agoThat exist(ed)! c2goasm would compile C and then decompile it into Go asm. https://github.com/minio/c2goasm https://github.com/minio/c2goasm