4 ms·
Achieving “not far away from C++ levels of performance” with anything but a procedural-style language is very difficult, rare, and usually comes with severe tr
by electrograv 7y ago
Achieving “not far away from C++ levels of performance” with anything but a procedural-style language is very difficult, rare, and usually comes with severe trade-offs, unfortunately.
For example: Haskell has been trying to bring the performance of C to a functional language for almost 30 years now. While Haskell is a great language with impressive performance for its category, I don’t think anyone believes it renders C/C++ obsolete for scenarios where predictably high performance is crucial.
This is an interesting topic to me, because while I try to find ways to write high-performance code as functionally as possible, I can’t seem to escape the relationship where more procedural-styled code usually yields consistently/predictably higher performance.
Functional (and other paradigms) can sometimes match C’s performance -- but in non-toy scenarios, the “sometimes” clause here compounds its probabilities to ultimately become “virtually never”, as the scope and complexity of a real project grows.
As a result, performance-critical projects written in languages without predictable performance characteristics often evolve into a situation later in development where 90% of your development time is spent poking at 'black boxes' (the compiler optimizer and garbage collector), hoping (sometimes futilely) that you can prod it into spitting out the procedural machine code patterns you already knew you needed anyway — if you’re lucky. And what makes this even worse is that this situation usually arises late enough into development that it's very costly (if not impossible) to backtrack and rewrite everything in a procedural language.
Of course, writing procedurally ends up trading off readability and robustness too human error, vs better performance. I too wish there was a better way to achieve both — I just haven’t found it yet.
- pdimitar 7y agoInsightful. I will be treading a very similar path soon. So, isn't there a way to emulate FP's `map` mechanics for example, in Rust, without losing performance? EDIT: To reflect your edits, this is why I want to learn Rust. I am as happy as one can be with Elixir due to Erlang's transparent super-robust and predictable parallel performance; this thing simply does not lag! However, I'd like to be prepared and be able to utilise Rust on the performance hot-spots.
- username90 7y agoThe major problem with functional programming is that many idioms requires a garbage collector. For example having pure operations on binary trees by just replacing dirty nodes leaving all old references intact wouldn't be possible in rust.
- pdimitar 7y agoIt isn't entirely impossible. Erlang's processes (actors, not actual OS processes; they are like mini-green threads / fibers) very often just throw away all their data after they are done with their work, and that data is often entirely stored entirely in the stack, without any GC needed -- even if the language is GC-ed. Also, all GC is per actor so there's no stop-the-world GC pauses. But the scenario you outline, yep, it does seem impossible with a borrow checker. And don't get me wrong. I am gradually learning Rust and I love it. But I can see myself being a little grumpy with the procedural style and often quirky syntax. Oh well, can't have it all, right?
- deleted 7y ago[deleted]
- electrograv 7y agoRegarding binary trees: You can always go back to using old-fashioned arrays and indices. A few simple contiguous arrays of nodes (e.g. a key array and value array) is often all you need, where nodes simply refer to each other via int indices into these arrays. You may be surprised that this often yields some of the best-performing data structures, sometimes better than those using raw pointers and non-contiguous heap allocations. And, this works in Rust just as it does in C. But at this point, you lose out on the benefits of Rust's static type system and borrow-checker: 1. The compiler will no longer be able to correctness-check the validity of these 'int' style references to other memory. 2. If something does go wrong and you read the array with a bad integer index, Rust will just 'panic' and crash the application. Unlike C, there will be no risk of memory errors or related security vulnerabilities. But on the other hand, each read being bounds-checked will make such Rust code slightly slower than is possible with C/C++. (Though I think you can use 'unsafe' blocks if you want to hyper-optimize akin to C.) But regarding GC langauges, I generally agree; they're almost always more trouble than they're worth in any performance-sensitive context. You often end up using approaches like this to optimize around the GC (e.g. int indexes into pre-allocated arrays) which ultimately means you're coding C-style in a GC language anyway, which defeats the whole point of GC's productivity-enhancing benefit -- at that point, why not just go all the way and use C or Zig or Rust etc.?