Repository navigation
Conversation
- Drop /d2vzeroupper-, -mno-vzeroupper and -mllvm -x86-use-vzeroupper=0 from FastSIMD_FastNoise - Remove ScopeExitx86ZeroUpper from the public Gen* functions; the compiler now clears the upper state on every exit back to non-AVX code
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
FastSIMD_FastNoisewas built with compiler-insertedvzeroupperturned off (/d2vzeroupper-,-mno-vzeroupper,-mllvm -x86-use-vzeroupper=0). Instead,ScopeExitx86ZeroUppercleared the upper state by hand at the 10 publicGen*exits. The aim was to skip clears inside FastNoise2 while still returning clean to application code that isn't compiled with AVX.Measured on a Ryzen 9 7950X (Zen 4), skipping the internal clears saves nothing, and the manual clear has costs of its own:
vzeroupperback on is perf-neutral: geomean 0.999 (clang-cl) and 1.000 (MSVC) on the default bench set. That holds even though the compiler then adds about 210 (clang-cl) or 500 (MSVC) clears per AVX feature set._mm256_zeroupperas clobbering all of ymm0–15. So with the RAII clear, every public function saves and restores xmm6–15, spills its result, and gets an exception-unwind funclet.GenSingle2Dis 52 instructions instead of 28.New<T>node factories use ymm stores, and the partial-vectormemcpycalls run with the upper state dirty. Neither had a measurable effect, but the compiler handles both automatically. Any new entry point would also have needed the RAII object added by hand.Fix
Remove the flags and
ScopeExitx86ZeroUpper. The compiler then insertsvzeroupperwherever AVX code returns to, or calls, non-AVX code.Clearing on exit still matters. On Zen 4, legacy-SSE code runs about 3.5x slower while any ymm upper half is dirty. This PR keeps that clear and lets the compiler place it.
Output change
None.
compareagainst master is bit-exact on all 1212 rows (SSE2, SSE41, AVX2 and AVX512; 2D, 3D and 4D), for both clang-cl and MSVC.Testing
Clean exits: an SSE-only caller read the AVX in-use state (
xgetbv(1)) after each of 15 public C API calls, on SSE41, AVX2 and AVX512. The ymm/zmm upper state is clean after every call, with both clang-cl and MSVC. The opmask and zmm16–31 state stays in use after any AVX-512 code.vzerouppercan't clear those, and they don't slow down legacy SSE code.Builds: clang-cl, MSVC, gcc 13 and clang 18 (Linux) all build without warnings.
Grid benchmark: 16384-point grids, 63 cases per feature set, medians across processes, PR time / master time:
A few AVX512 rows are slower: 2D White, Value and SuperSimplex on clang-cl (+4–8%), and 3D/4D Simplex, Value and White on MSVC (+5–10%). Removing only the RAII object, with the flag kept, slows the same rows by the same amount. So this comes from register allocation in the spill-heavy
GenUniformGridloop, not from the clears. On clang-cl AVX512 2D, the master build happens to avoid a 512-bit store-to-load forwarding stall.Calls from SSE-only code: I timed
GenSingle,GenSingleplus scalar SSE math, a 16x16 grid plus an SSE pass, and a 32³ chunk plus an SSE pass. All are within 1% of master, except AVX2GenSingle3Dplus SSE (0.97) and the 16x16 grid (0.87) on clang-cl, and AVX2GenSingle2D(0.94) on MSVC.