Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Go has a really neat stack allocation technique that makes #3 extremely non-wasteful compared to C and Rust threads. Unfortunately Rust cannot use the same trick as is is llvm based and llvm doesn't support that technique.


> Unfortunately Rust cannot use the same trick as is is llvm based and llvm doesn't support that technique.

This is a bit mistaken; ancient versions of Rust (but not so ancient that they weren't using LLVM) were, like Go, everything-is-implicitly-async, and Rust used the same strategy as Go to manage stacks (to the extent that the old language reference listed Go in its (rather long) list of precursors, specifically calling out its split stack approach).

The ultimate reason that Rust switched away from Go's approach is for interoperability with C. If your stacks are the size of C stacks, and if your language doesn't feature a pervasive runtime, then interoperability with C is nearly trivial, and this was something that Rust dearly desired (and has benefited greatly from, IMO).

Ultimately one should not look at Rust's chosen approach and assume that it represents some grand rebuke of alternative approaches to asynchronicity. If you have different constraints, then a Go-style approach is lovely. For a language at the level of Rust, different tradeoffs may dominate.


> If your stacks are the size of C stacks, and if your language doesn't feature a pervasive runtime, then interoperability with C is nearly trivial

in .NET stacks are different, the runtime is relatively large, yet interoperability with C is nearly trivial. From my PoV at least, I never worked on compilers, only used them a lot.


Interesting, though note that when I say "trivial" I'm not referring to the amount of effort it takes on behalf of the programmer using the feature, but the amount of work that needs to be done (and hence CPU time that needs to be spent) when crossing the boundary from one to another and back again (which is often invisible in the source and implicitly inserted by the compiler/runtime). In Rust there is no cost to traversing this boundary (the only hypothetical cost being that inlining is more difficult (though still technically possible, miraculously) across language boundaries).


> the amount of work that needs to be done (and hence CPU time that needs to be spent) when crossing the boundary from one to another and back again

That amount is really small for .NET. See this doc https://docs.microsoft.com/en-us/cpp/dotnet/calling-native-f... it says “PInvoke has an overhead of between 10 and 30 x86 instructions per call.” That’s barely measurable. Close to the cost of a mispredicted branch. 4x cheaper than a single cache miss.

That’s if you do it correctly, i.e. only marshal value types or arrays/pointers of them, don’t use custom marshallers, etc. Also it’s important to specify correct attributes. To call `int func(const sStruct&)` C++ function, you should specify `[In] ref sStruct` in C#.


It's easy to call C from .NET, but is it easy to call .NET from C? In a cross-platform manner?


Yes, and yes.

Declare a delegate in C#, apply [UnmanagedFunctionPointer] attribute specifying calling convention (on Linux you’ll likely want CallingConvention.Cdecl), pass to native code specifying [MarshalAs( UnmanagedType.FunctionPtr )] in the prototype of the DLL/SO function which accepts that pointer.

This is even more convenient than doing that in C. In C, function pointers are stateless, typical design pattern is pass accompanying `void* context` for the state. Not needed in C#, that delegate can capture whatever it wants, the runtime will do it’s marshalling magic, generating a native function and somehow associating it with the captured state.

The only caveat is lifetime. Function pointers can’t be retained by native code, they’re too simple and don’t have a ref.counter. You must ensure the .NET delegate is not garbage collected while it’s referenced by the native code, or the app will crash.


Go still needs to fallback to heavy stacks for c-ffi. In fact that’s the main reason rust dropped stackful coroutines, because you lose zero-cost ffi to c.


Do you have any links or details about this technique? It sounds interesting and I'd like to learn more.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: