Contents
A profile of Unreal Editor startup on the Electric Dreams sample project showed FSHA1::Update under “Compile Global Shader Map,” hashing shader source text. Lars Tofus reported 12.90 seconds spent there, summed over five launches with and without the change (ten runs total). With FXxHash64Builder, which Unreal already ships, the same work took 1.59 seconds. He calls that eight times faster.
The ratio is the least interesting part. Two other details decide whether you should copy the change.
CPU time is not startup time
Superluminal’s figure is CPU time summed across every thread that called the hash. Shader hashing runs on a worker pool, so the seconds are spread over many cores. The author notes that the effect on startup is roughly the measured time divided by the core count. On a 16-core machine, 11.3 saved CPU-seconds over five launches is a bit over 0.1 seconds of wall clock per launch, if the work parallelizes cleanly. That is a real saving and a cheap one, but it is not “the editor starts eight times faster,” and the post does not claim it.
Take this as a general rule for hash hot spots: a profiler that sums across threads tells you where cores are spent, which matters for build farms and CI minutes, and tells you less about the latency one person waits through.
The hash was a cache key
The input was shader source, comments and copyright notices included. The only job of the digest is to answer “has this source changed since I last compiled it?” Nobody is trying to forge a colliding shader to poison the cache, and the source is on the same machine as the cache. SHA-1’s collision resistance buys nothing here, and its throughput costs something.
The question to ask before swapping is who controls the input and what a collision does:
| Use of the digest | Attacker controls input? | Collision effect | Non-cryptographic hash OK? |
|---|---|---|---|
| Local build or shader cache key | No | Stale cache entry reused | Usually, if you can tolerate the odds below |
| Content-addressed storage fed by users | Yes | Wrong content served | No |
| Artifact or download integrity | Yes | Tampered file accepted | No |
The first row is a judgment about your pipeline, not a property of shaders. If a shared cache accepts entries from machines you do not control, the answer changes.
20 bytes to 8
SHA-1 digests are 20 bytes; xxHash64 is 8. The author notes that the change touched more code than expected because every place that stored or passed the hash had to change type. Stored hashes shrank from 20 bytes to 8, which is a small bonus for anything persisted per shader.
The cost is collision odds. By the birthday approximation, n inputs in a 64-bit space collide with probability about n²/2⁶⁵. For 100,000 distinct shader sources that is roughly 2.7 × 10⁻¹⁰, and for a million it is roughly 2.7 × 10⁻⁸ (my arithmetic, not from the source). Those are low for accidental collisions. The failure mode is also quiet: a collision would reuse a stale compiled result rather than error out. Whether that is acceptable depends on how large your key space gets and how a bad cache hit would surface.
The reviewer’s change matters more than the hash
Two review details from the post are worth copying. The first patch broke non-editor builds because the author had only built the editor, and an Epic reviewer fixed it. The reviewer also wrapped the digest in an FShaderHash struct. That means the next algorithm change touches the struct rather than every call site the author had to edit by hand. The author says xxHash64 is not necessarily the fastest choice and picked it because it was already in the engine and tested.
A raw uint8[20] threaded through a codebase is a decision you can only reverse by touching everything. A named type is a decision you can reverse in one place.
State of the change
At the time of writing the pull request (EpicGames/UnrealEngine #15237, as referenced in the post) had been accepted but not yet merged. I have not run the engine or reproduced the measurements, so the numbers above are the author’s, from one sample project.
If you want to check your own code for the same pattern, search for cryptographic hashes whose output is only compared for equality against a value you stored yourself, then profile before changing anything.