Also, graal can do some vectorization, and definitely has some libraries with vector intrinsics, e.g. truffle’s regex implementation uses similar algorithms to simdjson.
Code that uses jdk.incubator.vector does not actually get compiled to vector instructions under graal. This is why the top submission that uses jdk.incubator.vector is the only top solution that does not use graal.
I don't doubt that "similar algorithms to simdjson" may be used, but simdjson uses instruction sequences that are impossible to generate using the tools provided.
I mention these instructions because vpshufb comes up a lot in string parsing and formatting, because prefetch is useful for hiding latency when doing bulk accesses to a hash table, and because aesenc is useful for building cheap hash functions such as ahash.
Quick publication showing the difference (up to 125%!): https://www2.cs.arizona.edu/~dkl/Publications/Papers/ics.pdf
(and no, the story hasn't changed an awful lot since 2004. The gap still exists.)
Um... yeah it has. For starters, hotspot wasn't even a part of the JVM at that point. But further newer JVM additions like the enhanced for loop eliminate a ton of conditions where someone would run into bounds checking. Doing a naked `a[i]` is simply not common java code.
The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
It's a writing error on my end (clear since I qualify the percentage afterwards), so I think focusing on it distracts from the point (which is why I say it's pedantic).
I understand the pet peeve though (-:
HotSpot had been part of the JVM for five years at that point.
That said, in the paper they didn't use sun's JVM they used gcj and their own modified version of gcj.
> We examined the performance of both our new Java implementation as well as standard gcj on a variety of Java applications.
So my point still stands, a lot as changed. The researches chose to use static compilation over a JIT or interpreter.
It is extremely common in performance sensitive code, 1) graphics & rendering 2) networking 3) buffers
> But further newer JVM additions like the enhanced for loop eliminate a ton of conditions > The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
There are more comments in this thread that clarify further, but Java is very commonly unable to eliminate bounds checks. You can test all of these things yourself with a quick benchmark - don't take my word for it! The JIT is not as great at this as common rhetoric claims it is.
Also accessing byte arrays and direct buffers is extremely common - if you do just "business logic" jazz - it does not happen, though. However, every hashmap needs that, pretty much each hash lookup is a direct a[hash]
Perhaps the paper answers my question, but I'll admit I'm being lazy here and would much appreciate a tl;dr.
(with bounds checks) "Telling the Rust allocator to avoid zeroing the memory when allocating for Brotli improves the speed to 224 MB/s."
(without bounds checks) "Activating unsafe mode results in another gain, bringing the total speed up to 249MB/s, bringing Brotli to within 82% of the C code."
224MB/s -> 249MB/s (11% Brotli compression perf difference just by eliminating bounds checks)
If you write a micro-benchmark for only bounds-checks, you'd see the larger difference more inline with the "125%"
I don’t doubt that all of those things in the article were true back then, but that was eight years ago. Wow.
With that said, convincing a compiler to elide bounds checks (especially Java's JIT compiler) is a hugely frustrating (and for some algorithms futile) task.
It could be an argument that bounds checks make up a small percentage of total application performance. However, I've profiled production Java servers where >50% of the CPU was encryption/compression. JDK implementations of those algorithms are heavily impacted by (and commonly fail to elide) bounds checks.
Performance matters!
Adding explicit checks does work to a certain degree but it can change with the compiler, and it requires to keep checking the generated assembly - not fun (no unsafe, either but still)
Especially in Java, because "the assembly" can change as the JIT evolves. What is optimized today may not be tomorrow.
>What is optimized today may not be tomorrow.
Exactly. (Also most developers will have exceptionally hard time maintaining such code)
[0]: https://web.archive.org/web/20120328222841/http://cliffc.org...
I should play around this with this using a couple RISC-V cores.
> Performance matters!
https://www.youtube.com/watch?v=r-TLSBdHe1A by Emery Berger
Another, yesbut, encryption and compression are and will handled by on die accelerators.
The stuff gets worse with DirectByteBuffers as the JIT has to work harder. Unsafe allows to 'remove' all bound checks, but it may prevent some other optimizations.
I see this mentioned a few times in this thread, but I haven't experienced this in practice (and I've written a lot of unsafe in Java). Are there any examples of this?
Code cache is not completely relevant afaik - you can easily replicate in micro-benchmarks.
I don’t think it’s relevant here, the JIT compiler can do the same optimizations here.
If the branch predictor can basically 100% guess correctly (which will be the case in any correct program), it should not have any additional cost, besides taking up place in i$, so I would assume that that is responsible for the difference.
Correct. I was clarifying that the issue can be replicated in a "simpler" statically compiled benchmark.
> If the branch predictor can basically 100% guess correctly (which will be the case in any correct program), it should not have any additional cost
That isn't true. The CPU branch predictor has a cost no-matter what. Of course, it's a complicated story: https://blog.cloudflare.com/branch-predictor