Much of the difference in optimizability is that C/C++ allows you to do fairly extreme copy elision. This is important because memory bandwidth tends to be a major performance bottleneck.
C/C++ code can trivially reach into some remote bit of state and mutate it directly. The compiler rarely objects, no matter how ugly the access method or pattern. It is up to the programmer to make sure the invariants hold and the compiler can’t always see through it. These elided copies add up, though it usually has a maintainability and safety cost.
It is related to why functional languages tend to be mediocre for supercomputing in practice despite having nice properties for massive parallelism in theory.
Clojure creator Rich Hickey describes traditional imperative programming as "PLace Oriented Programming (PLOP)" when talking about how Clojure's approach is better for certain kinds of problems.
I agree with his point, and Clojure is my favorite programming language, but the computer itself is place-oriented. It runs on reading from and writing to locations in memory, and extracting maximum efficiency from it requires direct control over that memory.
Modern computers are exquisitely sensitive to spatial and temporal locality because many of the hardware features that speed your code up (for free!) take advantage of data or instructions that are “next to each other.” This can be a challenge in a functional program, because not only do you often have little control about where things go, the abstractions that make cool things possible (eg, closures, or lazy evaluation) naturally introduce indirection that hurts locality.
You can have great abstractions on the upper levels that lower to fast primitives. See the articles [1] Push your "ifs" up and "fors' down and [2] Vectorized interpreters for more details.
You could similarly argue that the computer is in fact fundamentally functional, since the core instruction cycle (Fetch Decode Execute) does not modify any memory.
The key insight into that system is that we've embedded a functional system within a mechanism that makes it imperative. The functional cycle can output commands to fetch it more data, that it can then use in the next cycle. It becomes imperative by the addition of time, which it needs because it's limited in the amount of input and output.
Are there any common resources for this kind of access methods and patterns that specifically aim to bridge the soft-/hardware gap? The best one I can come up with is Performance Analysis and Tuning on Modern CPUs[0] by Denis Bakhvalov.
I wonder if Roc will eventually have high-level abstraction to handle SIMD support. Perhaps by bringing some ideas from Futhark [1], a functional language focused on data parallelism. IIRC, the author has already been interviewed on Richard's podcast.
> lean-zip beats every single compression level miniz_oxide
Miniz_oxide is an intentionally simple implementation. It would be more fair to compare it to the newer zlib-rs[1], which even beats zlib-ng (fastest implementation in C)[2].
So, lean-zip "outperforming an equivalent Rust package that crates.io says has been downloaded over a billion times". Yes, zlib-rs has only been downloaded 200 million times... But it is way faster!
We should probably all be programming in a FP language, with behind the scenes an AI translating it to something faster using correctness preserving transforms.
I suspect this will be the best way to delegate things to AI while still staying in driver's seat.
Have you considered exposing these optimisations through a library so programmers can request optimisation passes and have the compiler check the preconditions?
Asking cuz I'm preparing an article on how fast JS can get and found the same thing, you can get in 10% of optimised C-code with basically any code if you torture V8 enough (excepting SIMD where there seems to be a bug in V8 where it doesn't optimise to SIMD even when beyond obvious). Figured out I could extract it in a lib and do it more streamlined but not sure how well manual transforms generalise.
Also, when working on the roc-deflate, did you find recurring transformations that could fit that model or were most improvements specific to compression?
Yes, it does beat -O3 C sometimes. I took code from diverse 'language speed' benchmarks from the internet and had to rearrange them to get -O3 to beat equivalent-looking JS (Node 25.8.1 w\ V8 14.1 and clang 21 on MBP3).
i've tried various options, Firefox prefers ` for (let i = n; i--;) out[i] = a[i] * b[i] + 0.25;`
Its already at peak optimization so no form is significantly better.
saying it "beats a crate that has been downloaded X times" is not saying anything other than that is popular. It'd be nice to see slightly more detail there.
This is kind of hacker news post and comments I like; extreme languages on extreme performance with extreme features, not types being added to a language ten gizzilion times slower than a tortoise and the post making anti-type flat-earthers getting freaked out if they have to vaccinate next.
In the age of powerful LLMs, is there really any reason to program in any higher abstraction than C? Seems like most other languages can be put out to pasture. They were just compromises and shortcuts to make things easier for humans.
Because LLMs make mistakes, and you want to prevent that?
No wonder Rust is becoming the default language for high-performance LLM-written code. I hope Roc becomes a viable option soon, as it reaches stability.
Problem specifically in Haskell is that you have to worry about implementing tail recursion to get efficient stack usage. That's not necessary for loops, which Haskell doesn't support. Though other functional languages do support them.
Digging through the current Haskell standard library - for_ calls traverse_, which calls foldr, which calls foldMap, which calls… foldr. Found the recursion! The person you’re replying to is correct - you can mimic the style of a for loop, but under the hood all of this stuff is going to be implemented with recursion (and tail recursion, if you don’t want to hate your life).
I also may have gotten some detail slightly wrong, the code in Data.Foldable is kinda weird and I haven’t Haskell’d in a while
Constructs like whileM_ require extra IORef effort to the point where people actually recommend recursion (with tail recursion) instead, which is still harder to implement than conventional while loops.
This seems to raise the question: is libdeflate fast? My overall impression of libdeflate is that it produces a similar output size to zstd but takes 40 times longer to do it. I realize these are different algorithms of course, but they aren't that different, both being in the Huffman line of the LZ family. So why is libdeflate considered a performance benchmark?
It’s probably just because a zstd implementation in Lean would be more difficult. The whole project depends on being able to have a correctness proof for the implementation, and implementing that for the DEFLATE algorithm is simpler.
There are no well known programs made with Roc. It's been usable for writing real software since July or so for those of us willing to deal with compiler crashes and regular syntax/feature changes. To my knowledge there are six people producing substantial amounts of software in Roc. I'm using it for a speech recognition/transcription app and the desktop environment and apps on my eink tablet, both on my own UI framework.
Well, it has not even reached 0.1.0, so the language is still in early development phase. But it has interesting properties, so I would recommend experimenting with it.
C in indeed a functional language in that most C projects are things that have been observed in real life and have subsequently been uploaded into computers.
I'd also like to note that performance is the ULTIMATE concern in a lot of cases.
If you have a server that is making 10million requests to a certain method in a minute, then you cannot afford any performance compromise. Any, at all.
Rust will never replace C/C++ with regard to high performance computing.
> Rust will never replace C/C++ with regard to high performance computing.
I don't buy this. Rust is a lot easier to make more performant than C++ and doesn't really trail behind C in anything but .text size last time I looked. Considering you can easily write C-like Rust that ends up in the same LLVM stack as Clang (more or less) I just don't think this is true outside of some very specific contexts.
> Rust is a lot easier to make more performant than C++
I have never seen evidence of this in the wild. C++ is still significantly faster than Rust in practice when doing performance-sensitive parallel development in the two languages.
The reason for the performance gap is mostly understood. Rust's codegen is currently somewhat worse for a variety of reasons. Rust's much weaker compile-time metaprogramming features make some optimizations impractical in the language that are easy in C++. Culturally, "unsafe" minimization in Rust pessimizes code architecture.
Rust can get pretty close to C++ but it often requires writing some very ugly Rust.
> C in indeed a functional language in that most C projects are things that have been observed in real life and have subsequently been uploaded into computers.
I don’t think you share the same understanding of functional as TFA uses.
Not functional as in “working”/functioning. Functional is meant here as in functional programming, the paradigm.
Maybe I am misunderstanding what you wrote, but "state" in the context of functional programming refers to the state internal to the program, not the state external to the program. So whether the input data comes from the real world is orthogonal to the question of whether a program preserve state.
There are no authoritative definition AFAIK, but this is a way too coarse definition to qualify for me at least.
I think many equate FP with referential transparency, garbage collection, first class functions/closures. But usually it’s a sort of mix of all the above or opt-in. Standard ML makes it easy to mutate state for instance.
C is by no means a functional language. It is perhaps the definition of imperative; the poster child.
> Roc is not a mature language (it's still weeks away from its first numbered release, 0.1.0)
It then links to a video where a consultant mentions using in production (for several years already?), which confused me.
How can you justify using immature technologies for client work? Or is it an extra benefit when it breaks and you have to fix it?
I'm genuinely curious, maybe it's not as bad as I think. But I've used immature hosting platforms, and that certainly didn't go well for me!
Much of the difference in optimizability is that C/C++ allows you to do fairly extreme copy elision. This is important because memory bandwidth tends to be a major performance bottleneck.
C/C++ code can trivially reach into some remote bit of state and mutate it directly. The compiler rarely objects, no matter how ugly the access method or pattern. It is up to the programmer to make sure the invariants hold and the compiler can’t always see through it. These elided copies add up, though it usually has a maintainability and safety cost.
It is related to why functional languages tend to be mediocre for supercomputing in practice despite having nice properties for massive parallelism in theory.
Clojure creator Rich Hickey describes traditional imperative programming as "PLace Oriented Programming (PLOP)" when talking about how Clojure's approach is better for certain kinds of problems.
I agree with his point, and Clojure is my favorite programming language, but the computer itself is place-oriented. It runs on reading from and writing to locations in memory, and extracting maximum efficiency from it requires direct control over that memory.
Modern computers are exquisitely sensitive to spatial and temporal locality because many of the hardware features that speed your code up (for free!) take advantage of data or instructions that are “next to each other.” This can be a challenge in a functional program, because not only do you often have little control about where things go, the abstractions that make cool things possible (eg, closures, or lazy evaluation) naturally introduce indirection that hurts locality.
You can have great abstractions on the upper levels that lower to fast primitives. See the articles [1] Push your "ifs" up and "fors' down and [2] Vectorized interpreters for more details.
[1] https://debasishg.github.io/blog/push-ifs-up-fors-down/ [2] https://venge.net/graydon/talks/VectorizedInterpretersTalk-2...
> but the computer itself is place-oriented
You could similarly argue that the computer is in fact fundamentally functional, since the core instruction cycle (Fetch Decode Execute) does not modify any memory.
The key insight into that system is that we've embedded a functional system within a mechanism that makes it imperative. The functional cycle can output commands to fetch it more data, that it can then use in the next cycle. It becomes imperative by the addition of time, which it needs because it's limited in the amount of input and output.
If a function is computed in the woods, does it return a result?
Only if it has a place to put the result.
That's a nice explanation, thanks.
Are there any common resources for this kind of access methods and patterns that specifically aim to bridge the soft-/hardware gap? The best one I can come up with is Performance Analysis and Tuning on Modern CPUs[0] by Denis Bakhvalov.
[0]: https://github.com/dendibakh/perf-book
I recently came across https://www.kernel.org/pub/linux/kernel/people/paulmck/perfb... , it draws from author's experience about applying optimizations in linux codebase .
I also like this very readable paper https://xnning.github.io/papers/perceus.pdf, about reducing the costs of reference counting in context of functional languages.
I wonder if Roc will eventually have high-level abstraction to handle SIMD support. Perhaps by bringing some ideas from Futhark [1], a functional language focused on data parallelism. IIRC, the author has already been interviewed on Richard's podcast.
[1] https://futhark-lang.org/
> lean-zip beats every single compression level miniz_oxide
Miniz_oxide is an intentionally simple implementation. It would be more fair to compare it to the newer zlib-rs[1], which even beats zlib-ng (fastest implementation in C)[2].
[1]: https://lib.rs/crates/zlib-rs
[2]: https://trifectatech.org/blog/zlib-rs-is-faster-than-c/
So, lean-zip "outperforming an equivalent Rust package that crates.io says has been downloaded over a billion times". Yes, zlib-rs has only been downloaded 200 million times... But it is way faster!
downloads is such a weird metric to focus on
We should probably all be programming in a FP language, with behind the scenes an AI translating it to something faster using correctness preserving transforms.
I suspect this will be the best way to delegate things to AI while still staying in driver's seat.
Have you considered exposing these optimisations through a library so programmers can request optimisation passes and have the compiler check the preconditions?
Asking cuz I'm preparing an article on how fast JS can get and found the same thing, you can get in 10% of optimised C-code with basically any code if you torture V8 enough (excepting SIMD where there seems to be a bug in V8 where it doesn't optimise to SIMD even when beyond obvious). Figured out I could extract it in a lib and do it more streamlined but not sure how well manual transforms generalise.
Also, when working on the roc-deflate, did you find recurring transformations that could fit that model or were most improvements specific to compression?
IIRC in weird corner cases, JS code can outperform wasm/C/C++ because JIT optimizes with escape analysis/inlining.
>where it doesn't optimise to SIMD even when beyond obvious What was that code?
Yes, it does beat -O3 C sometimes. I took code from diverse 'language speed' benchmarks from the internet and had to rearrange them to get -O3 to beat equivalent-looking JS (Node 25.8.1 w\ V8 14.1 and clang 21 on MBP3).
> What was that code?
literally:
``` function numericKernel(a, b, out, n) {
} ```i've tried various options, Firefox prefers ` for (let i = n; i--;) out[i] = a[i] * b[i] + 0.25;` Its already at peak optimization so no form is significantly better.
saying it "beats a crate that has been downloaded X times" is not saying anything other than that is popular. It'd be nice to see slightly more detail there.
This is kind of hacker news post and comments I like; extreme languages on extreme performance with extreme features, not types being added to a language ten gizzilion times slower than a tortoise and the post making anti-type flat-earthers getting freaked out if they have to vaccinate next.
And it's something they've been working on for the past 8 years!
In the age of powerful LLMs, is there really any reason to program in any higher abstraction than C? Seems like most other languages can be put out to pasture. They were just compromises and shortcuts to make things easier for humans.
Because LLMs make mistakes, and you want to prevent that? No wonder Rust is becoming the default language for high-performance LLM-written code. I hope Roc becomes a viable option soon, as it reaches stability.
Problem specifically in Haskell is that you have to worry about implementing tail recursion to get efficient stack usage. That's not necessary for loops, which Haskell doesn't support. Though other functional languages do support them.
Haskell most definitely supports loops:
It doesn't require loops to be implemented at the language level, however. The language is rich enough to support user-defined loop constructs.Digging through the current Haskell standard library - for_ calls traverse_, which calls foldr, which calls foldMap, which calls… foldr. Found the recursion! The person you’re replying to is correct - you can mimic the style of a for loop, but under the hood all of this stuff is going to be implemented with recursion (and tail recursion, if you don’t want to hate your life).
I also may have gotten some detail slightly wrong, the code in Data.Foldable is kinda weird and I haven’t Haskell’d in a while
Constructs like whileM_ require extra IORef effort to the point where people actually recommend recursion (with tail recursion) instead, which is still harder to implement than conventional while loops.
This seems to raise the question: is libdeflate fast? My overall impression of libdeflate is that it produces a similar output size to zstd but takes 40 times longer to do it. I realize these are different algorithms of course, but they aren't that different, both being in the Huffman line of the LZ family. So why is libdeflate considered a performance benchmark?
It’s probably just because a zstd implementation in Lean would be more difficult. The whole project depends on being able to have a correctness proof for the implementation, and implementing that for the DEFLATE algorithm is simpler.
https://github.com/kim-em/lean-zstd
The pure Lean implementation of zstd in that project is only decompression, not compression. It kind of proves my point.
I'm very simple. I see Richard Feldman post. I like it. I'll leave it to the experts as to whether this is a case of opportunistic mutation.
What are some well known programs made with Roc? And I'm curious about the difficulty level of its syntax
There are no well known programs made with Roc. It's been usable for writing real software since July or so for those of us willing to deal with compiler crashes and regular syntax/feature changes. To my knowledge there are six people producing substantial amounts of software in Roc. I'm using it for a speech recognition/transcription app and the desktop environment and apps on my eink tablet, both on my own UI framework.
Well, it has not even reached 0.1.0, so the language is still in early development phase. But it has interesting properties, so I would recommend experimenting with it.
C in indeed a functional language in that most C projects are things that have been observed in real life and have subsequently been uploaded into computers.
I'd also like to note that performance is the ULTIMATE concern in a lot of cases.
If you have a server that is making 10million requests to a certain method in a minute, then you cannot afford any performance compromise. Any, at all.
Rust will never replace C/C++ with regard to high performance computing.
> Rust will never replace C/C++ with regard to high performance computing.
I don't buy this. Rust is a lot easier to make more performant than C++ and doesn't really trail behind C in anything but .text size last time I looked. Considering you can easily write C-like Rust that ends up in the same LLVM stack as Clang (more or less) I just don't think this is true outside of some very specific contexts.
> Rust is a lot easier to make more performant than C++
I have never seen evidence of this in the wild. C++ is still significantly faster than Rust in practice when doing performance-sensitive parallel development in the two languages.
The reason for the performance gap is mostly understood. Rust's codegen is currently somewhat worse for a variety of reasons. Rust's much weaker compile-time metaprogramming features make some optimizations impractical in the language that are easy in C++. Culturally, "unsafe" minimization in Rust pessimizes code architecture.
Rust can get pretty close to C++ but it often requires writing some very ugly Rust.
AFAIK, I could be wrong or ill informed about this.
But, Rust does indeed embedded extra code for much of the functionality that it adds ontop of what C/C++ has.
This obviously has a performance hit. Might not be observable on a desktop application but it will be evident on server applications.
> C in indeed a functional language in that most C projects are things that have been observed in real life and have subsequently been uploaded into computers.
I don’t think you share the same understanding of functional as TFA uses.
Not functional as in “working”/functioning. Functional is meant here as in functional programming, the paradigm.
AFAIK, functional programming is programming that doesn't modify state.
And, if all you're doing is taking material from real life and uploading into computers, then you're not modifying state at all.
Maybe I am misunderstanding what you wrote, but "state" in the context of functional programming refers to the state internal to the program, not the state external to the program. So whether the input data comes from the real world is orthogonal to the question of whether a program preserve state.
There are no authoritative definition AFAIK, but this is a way too coarse definition to qualify for me at least.
I think many equate FP with referential transparency, garbage collection, first class functions/closures. But usually it’s a sort of mix of all the above or opt-in. Standard ML makes it easy to mutate state for instance.
C is by no means a functional language. It is perhaps the definition of imperative; the poster child.
[dead]
[dead]