Add WebAssembly SIMD128 implementation and Node.JS support #825
No reviewers
Labels
No labels
announcement
bug
build
CI
dependencies
documentation
duplicate
enhancement
experience feedback
feature request
github_actions
help wanted
install
invalid
Long-term
optimization
portability
question
target-specific
test issue
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-Cyan4973/xxHash!825
Loading…
Reference in a new issue
No description provided.
Delete branch "wasm128"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
<arm_neon.h>XXH3_NEON_LANES != 8IS BAD since SIMDe scalarizes half vectorsmake NODE_JS=1)nodefsandnoderawfsto access local filestty.isatty()from Node instead of the brokenisatty()in libc (which always returns1for stdin/stdout/stderr)wasm/asmjsto the welcome messageemccoutput files onmake cleanDue to the fact that WebAssembly is JIT-compiled and that JavaScript timer precision is limited due to Spectre, the results may vary.
NEON gets almost native speed on v8, but x86_64 does not mainly due to the instruction sets not exactly matching up. Firefox's aarch64 JIT seems to be considerably slower when it comes to WASM.
xxhsum -bon an AArch64 Google Tensor G1 (Cortex-X1 @ 2.80 GHz):x86_64 (with AVX2) Core i7-8650U @ 2.11 GHz (turbo 4.20 GHz):
(Not sure why XXH128 is that much faster on x86 but not ARM. Maybe code alignment memes?).
The added source code is pretty clean, this is easy to review.
It seems you want to test it a little longer ?
In which case, could you make this PR "draft", so I don't wonder if it needs to be merged now or later ?
So apparently marking it as a draft cancels CI tests...
Ok I think I figured everything out. I want to add various versions of the Emscripten SDK to the matrix but that isn't a huge priority. I mostly want to test the recent node.js versions.
Amusingly due to the
clock()granularity (due to Spectre mitigations),xxhsum -bi0has only a few possible entries.This seems to be ready to merge, porting the NEON code was dead simple. Sorry I had to do it the old fashioned way because CI tests were ignored.
minor nit: alignment
doc: since what ?
was more explanation expected there ?
This looks good to me.
It fits well in existing code base.
I would recommend publishing a set of benchmark as part of this PR description, as it can serve as reference for future users of the
WASM128code path.That was probably my cat lol
Fixed
A basic xxhsum -b benchmark is up now.
Also, I managed to get a bench on Firefox. It isn't as impressive but it is still a solid improvement on x86 and just a slight decrease on aarch64.
Actually....I have an idea.
emcc emulates NEON intrinsics with
SIMDe, and the intrinsics are so similar that I am considering scrapping the intrinsics and using NEON with some slight modifications.I literally get
the sameBETTER performance if I just replace the multiply in scrambleAcc and put an asm guard on the secret pointer to keep it from turning it into literals.Screw these intrinsics. I'm putting wasm on
XXH_NEONwith a couple ofifdefs.🦀 SIMD128 intrinsics are GONE 🦀
SIMDe generates the same thing, and I was actually able to make it faster, now reaching 16.3 GB/s on my phone. (Mostly due to stopping Clang from constant folding kSecret).
I also found a stupidly obvious optimization to
XXH3_scrambleAcc_neon. 🤦♀️I will get some new benchmarks tomorrow and probably squash.
Apparently I can't figure out how
git rebaseworks and my commits doubled lolEdit: fixed. Aside from the CI test commit there is no trace of the intrinsics now 😏
Ok so apparently aarch64 Firefox has slowed down even more.
However, I am not that concerned since aarch64 is likely going to be using V8 or WebKit anyways — Firefox for Android has like a 0.6% usage share lol, and it is still a reasonable speed. Looking over the source code it might be that the compiler can't hoist the shuffle masks out of the loop, but there isn't much I can do. Shuffle masks are inlined with the instruction. The only ones that would probably use it are ARM Macs and AArch64 Windows/Linux users.
Also I want to see how Safari (WebKit) on iOS fares sinceApparently WebKit doesn't support SIMD.emruncan host a web server.The last commit is just documentation.
Coming back to this topic, trying to re-acquire the context.
Initially, this PR was providing a new vector code path for WASM.
Doing so would improve performance on WASM target, which currently can only use the
scalarcode path.This code was initially reviewed, and accepted.
Quickly after, @easyaspi314 discovered that the WASM compiler
emcccould actually ingest NEON intrinsics directly, resulting in similar performance, but removing the need for additional dedicated code path, which is good for maintenance.The PR was then substantially altered to take advantage of this property, resulting in a generally shortened implementation.
Then, it was discovered that the "similar performance" (as WASM SIMD128) statement is not entirely correct.
This is true when running with node.js for
aarch64target.But when it comes to
x64, some performance is lost (compared to WASM SIMD128), because NEON intrinsics are different enough from AVX ones. The counter to this point is that using NEON intrinsics for WASM still delivers some hefty speed benefit compared toscalar, so the PR is still a net progress.Another problematic issue is that Firefox for Android actually suffers from the new NEON intrinsic code path, even as the local target is
aarch64, to the point of being even slower thanscalarcode path, making this PR no longer a "guaranteed positive". The counter to this point is to state that Firefox has probably a negligible presence onaarch64.It would have been appropriate therefore to complete the picture by presenting benchmarks using another system (Chrome maybe?) that is presumed more representative of
aarch64scenario.Anyway, this is what I recall from memory, and since this PR's content has changed substantially since it was first validated, it deserves another review.
@easyaspi314 , is that a correct assessment of the situation ?
V8 and NodeJS use Chrome's JavaScript engine.
I am not that concerned about the Firefox AArch64 problem:
I should update and run some new benchmarks on the latest versions, as well as get a Safari benchmark as iOS 16 should have SIMD now. (Unfortunately my MBP is out of commission so Safari x64 might be tricky to bench unless I can find a WebKit impl for Windows)
As for validation, the NEON intrinsics we use are literally a 1:1 translation to SIMD128 aside from the umlal needing an additional add, so it is as optimal as wasm intrinsics unless I am missing something.
Found that I can run WebKit via Epiphany on Linux.
WebKit x64 isn't doing too hot.
scalar
simd128
Apparently, this is because WebKit converts
extmulto zero extend + full multiply without a special case, and then it is forced to scalarize because it will only emit a i64x2 multiply if AVX-512 is enabled. (NEON, x64). I might open up a PR/issue for this to be added because this is pathetic 💀However, I don't quite understand what is up with Firefox's aarch64 jit. I might have to boot up my raspi so I can get the JIT debugger. (Edit: Really? No Linux aarch64 builds?! Do I seriously have to install tiny11 for this?)
Well I got a build of Firefox's jsshell for AArch64 that can dump native code, but the standard emcc wrappers don't work with it. I might have to write my own WASM glue. 💀
I am truly baffled on how the performance is worse, as the reason isn't blatant like WebKit — all instructions should be lowered to the direct NEON counterparts.
Also I tried adjusting the JIT parameters on Firefox, to no avail. And it isn't baseline jit being used because when I disable optimizing jit it is only 2.9 GB/s.
So, to summarize the situation,
this PR influences performance of
XXH3when compiled for WASM,under Node.JS, it's much faster (2x-3x),
but under Web Browser environment, whether it is Chrome of Firefox,
it's only a bit faster for x64 cpus (+50%)
but it's slower for
aarch64cpus (-20-30%)even though the WASM code path uses
NEONintrinsics.Correct ?
Basically although a little mixed up.
For all intents and purposes, Chrome and Node.JS are the same. I have confirmed that the benchmarks are nearly identical because they use the same JS engine (V8) under the hood. Chrome pairs V8 with the Chromium HTML renderer, Node.JS pairs V8 with an OS interface.
Rough diagram of the browser situation:
Firefox and Chrome (and derivatives) are on a rapid release cycle, and the only time you see an old JS engine is an older version of Node.JS (which doesn't matter because it is already a performance improvement), an unsupported OS, or someone who refuses to update. You rarely come into the issue where someone is stuck with an old toolchain as well because of how
emccis distributed.WebKit only recently added SIMD128 so it is unsurprising that there are a few kinks. This one is very simple and could be fixed in a few hours once I figure out how to contribute.
Firefox AArch64 is probably also a simple bug that I can't tell from the source code itself, given how it should be a direct translation. I mostly need to find a minimal reproducible example for the codegen bug to report it.
However, the important thing is that the code that is generated is optimal on the WASM level, and this is a problem with the upstream browsers. I say merge and then we try to fix the Firefox and WebKit bugs.
Additionally, the most important target is V8 because most browsers are Chromium based, and Node.JS will affect server-side performance which is very significant on a large server.
@ -55,6 +55,13 @@ elseEXT =endififeq ($(NODE_JS),1)This environment variable
NODE_JS=1must be set at compilation time.It should probably be documented in
README.md.I'm going to insist a bit regarding the documentation of the new
NODE_JSbuild variable,since I don't know if I would be able to complete it myself.
I see
NODE_JSused in tests, but I don't know if this variable is meant to be used only for tests, or meant to be used only with node.js specifically, if it can ignored at (presumably) the cost of performance, etc.Done. I also did things a bit more correctly, added a helpful note to the make check output if it fails, and removed the
RUN_ENVoverride.NODE_JSadds proper detection of terminals (as the default emsdk's isatty is a no-op) and more importantly gives unrestricted access to the filesystem.