News #13:
I’ve recently added new comparisons to the stress tests, measuring the performance of code generated by our backends against fully hand-optimized, imperative native equivalents (using the Native FFI).
We are consistently observing a ~2x performance factor between the compiled PureScript code and the raw native implementations. This is quite an interesting threshold.
When you consider the Arista ES backend (which features advanced optimizations for JS) and our own gopurs backend, both seem to plateau around this exact same overhead ratio. Having spent significant time trying to squeeze out a few more milliseconds from the Go compiler myself, and facing increasing difficulty doing so, it strongly suggests we might be reaching the hard limits of optimization here. There is an inherent, irreducible cost to maintaining high-level functional abstractions (like deep closures, persistent immutability, and recursion) on targets that aren’t purely designed for them.
But this is actually great news, because the factor is fine, and it brings us to a fundamental philosophy about how we should write and build applications:
These stress tests are deliberately unoptimized. They are designed as boundary cases to push the compilers to their absolute limits. In a real-world project, 99% of the codebase will perform remarkably close to native speeds.
This performance is more than enough for the vast majority of our work, and it grants us the immense privilege of focusing entirely on our high-level domain concepts, safely ignoring the low-level machinery details. For the remaining 1% (those critical algorithmic hot paths) we have three options: accept a 2x slowdown, use safe mutability abstractions like the ST monad (which compile down to highly efficient imperative loops), or seamlessly drop down into FFI to write raw, imperative code as close to the metal as needed.
At this stage, optimization gains are often on the scale of mere microseconds. While further improvement is always possible, chasing these micro-optimizations is no longer a priority for me, right now.
Just one exception: Go 1.27 has been released . Huge improvements, especially around generics and polymorphism. That could unlock further optimizations for gopurs. I’ll try that in the coming hours/days.






