Change default x86 build target from x86-64-v2 (SSE4.2) to x86-64-v3 (AVX2) by Algunenano · Pull Request #90043 · ClickHouse/ClickHouse · GitHub
Skip to content

Change default x86 build target from x86-64-v2 (SSE4.2) to x86-64-v3 (AVX2)#90043

Merged
Algunenano merged 117 commits into
ClickHouse:masterfrom
Algunenano:x86v3
May 13, 2026
Merged

Change default x86 build target from x86-64-v2 (SSE4.2) to x86-64-v3 (AVX2)#90043
Algunenano merged 117 commits into
ClickHouse:masterfrom
Algunenano:x86v3

Conversation

@Algunenano

@Algunenano Algunenano commented Nov 14, 2025

Copy link
Copy Markdown
Member

Changelog category (leave one):

  • Backward Incompatible Change

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

This change was reverted

Documentation entry for user-facing changes

  • Documentation is written (mandatory for new features)

The default ClickHouse binary for x86 Linux now requires AVX2 (x86-64-v3). CPUs without AVX2 support will fail with an "Illegal instruction" error. For systems without AVX2, use the compatibility build (amd64compat) available at https://builds.clickhouse.com/master/amd64compat/clickhouse. The install script (curl https://clickhouse.com/ | sh) automatically detects CPU capabilities and downloads the appropriate build.

Closes #99172

Version info

  • Merged into: 26.5.1.594

@Algunenano Algunenano self-assigned this Nov 14, 2025
@clickhouse-gh

clickhouse-gh Bot commented Nov 14, 2025

Copy link
Copy Markdown
Contributor

@clickhouse-gh clickhouse-gh Bot added the pr-not-for-changelog This PR should not be mentioned in the changelog label Nov 14, 2025
@Algunenano

Copy link
Copy Markdown
Member Author

It shows both improvements and regressions in some cases. I'll keep it open to review it in depth and see if we can introduce dynamic dispatching where it benefits

@Algunenano

Copy link
Copy Markdown
Member Author

Merged with master after a few of the improvements have been introduced via dynamic dispatch. I'll keep investigating other speed ups

@Algunenano

Algunenano commented Dec 2, 2025

Copy link
Copy Markdown
Member Author

Next to investigate is memcpy: We use our own, which is optimized for SSE4.2 and does 128 bit copies. AVX2 does 256 bit copies, and AVX512 would allow us to copy 512 bit per instruction. There was quite a bit of work done to introduce our own (#21520).

Some ideas:

Something that did not work: Prefetching

@Algunenano

Copy link
Copy Markdown
Member Author

I tried different alternatives for memcpy but keeping our currently implementation seems the best option so far

Algunenano and others added 3 commits February 20, 2026 18:54
In `range.cpp` `executeConstStartStep` and `iota.cpp` `iota`/`iotaWithStep`,
replace `start + idx * step` with an accumulator `value += step`. This avoids
per-element multiplies (e.g. `pmaddubsw` for UInt8) that cause the compiler to
choose a high auto-vectorization threshold (128 bytes for AVX2 vs 32 for SSE),
making the vectorized path unreachable for short arrays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nickitat

Copy link
Copy Markdown
Member

Idk, if this could be useful for you or not. Afaik, the most conservative way to enable AVX2 would be to set -mprefer-vector-width=128 and +allow-light-256-bit (this article has more details). And yes, in the AVX2 context, it should be relevant only to older Intel chips, and if we're determined to enable, we should do so without crutches.

@Algunenano

Copy link
Copy Markdown
Member Author

We currently use the default tuning for x86-64-v3 in clang which contains a bunch of tunings, including +allow-light-256-bit. But for example, when using dynamic dispatch for v4+ we had to add no-prefer-256-bit because the performance was noticeably worse in some cases, mostly because older Intel might need it but newer AMD became worse. It might be tricky to find one specific set up that works best for everything.

@rschu1ze

Copy link
Copy Markdown
Member

The cleanest approach is to assume a processor without throttling, therefore we won't need crutches (prefer-vector-width=128, allow-light-256-bit, no-prefer-256-bit etc.). ClickHouse Cloud runs on ARM anyways.

On x86-64-v3 (AVX2), the compiler semi-vectorizes the `UInt64`->`Float32`
conversion loop using `vpand`/`vpsrlq`/`vpor`/`vblendvpd` for unsigned
fixup, scalar `vcvtsi2ss` with extract/insert, and `vblendvps` for
conditional selection. This ~20-instruction sequence per 4 elements is
slower than simple scalar code (~5 instructions per element with
perfectly-predicted branches), because there is no AVX2 vector instruction
for int64->float conversion (only AVX-512 has `vcvtqq2ps`).

Move the `UInt64`->`Float32` specialization out of the `__aarch64__` guard
and add `_Pragma("clang loop vectorize(disable)")` on x86 to generate
clean scalar code with VEX encoding and 8x unrolling.

Benchmark: `sum(toFloat32(number))` goes from ~9% regression to ~20%
improvement on v3 vs v2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Algunenano

Copy link
Copy Markdown
Member Author

Most of the regressions should be covered now. The remaining red tests that I see are all within or at threshold. For example, bigint_arithm query 7 is +18.9% with threshold 18.9% and when testing locally v3 is slightly faster. I'll do another round of pure performance test before switching to check the whole CI.

The default x86 build now requires x86-64-v3 (AVX2). Update the install
script to check for `avx2` instead of `sse4_2` in `/proc/cpuinfo`, falling
back to the `amd64compat` build on older hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Algunenano

Copy link
Copy Markdown
Member Author

Performance test noise analysis

The performance comparison reports show elevated thresholds and a high number of borderline regressions. This is expected and not indicative of real regressions.

Root cause of the noise: this PR compares a v2 (SSE4.2) baseline against a v3 (AVX2) build. Unlike normal PRs where both old and new binaries share the same ISA target and produce nearly identical machine code, here the two binaries have fundamentally different code generation — different instructions, different vectorization decisions, different instruction scheduling. The perf test alternates between old and new binary runs, and the different ISA targets leave different residual state in CPU caches, branch predictors, etc. This causes variable performance deltas between runs, leading to high variance and high thresholds.

Statistical summary (AMD shards, sha 8ba3244):

  • 2090 queries tested
  • 314 improvements above threshold vs 104 regressions above threshold (3:1 ratio)
  • 87 of 104 regressions are within 2% of their threshold — consistent with the expected ~5% false positive rate
  • Only 2 regressions significantly exceed their threshold, both on queries with very small absolute times (<12ms) where variance is inherently high

Local verification: all CI-reported regressions were benchmarked locally (master binary from CI vs local v3 build). None showed real regressions:

  • not(toFloat32(number)) — reported at +40.4% in CI, locally identical (~0.17s on both)
  • sum(toFloat32(number)) — v3 is faster after the vectorize(disable) fix
  • sumKahan(toNullable(toFloat32(number))) — same speed
  • kostikConsistentHash, number % 10 GROUP BY, intDiv(toInt256), decimal aggregates — all same speed or v3 faster

Note: the sum(toFloat32(number)) query [4] did not run in the latest CI execution, so the fix for that specific query cannot be confirmed from CI yet. A re-run should pick it up.

This noise will disappear once v3 becomes the baseline — future PRs will compare v3 vs v3, restoring normal threshold levels.

pull Bot pushed a commit to admariner/ClickHouse that referenced this pull request May 8, 2026
Under MSan, `SelectStreamFactory::createForShardWithAnalyzer` calls
`InterpreterSelectQueryAnalyzer::getSampleBlockAndPlannerContext` once
per shard. Each invocation runs a full `only_analyze` `Planner` pass
which costs ~140 ms × 20 shards = ~3 s on the initiator before the
first remote sub-query is even sent. Combined with the staggered
sub-query execution (~5.5 s for 20 × 1.4 s shards) the total
wall-time consistently lands in the 9.5–10.2 s range, tipping over
the 10 s `timeout` in the test.

The same code path runs on every build, but only MSan inflates each
analyze pass enough to push the test past its budget. Tag the test
as `no-msan` and update the stale comment that still refers to a
5 s budget (the test was switched to 10 s years ago).

CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=90043&sha=a61ed3dbbc4da693beb4601322c3f8f6d8b2f8b0&name_0=PR&name_1=Stateless+tests+%28amd_msan%2C+WasmEdge%2C+parallel%2C+2%2F2%29
PR: ClickHouse#90043
Algunenano and others added 2 commits May 10, 2026 15:57
The previous `${OBJCOPY/%objcopy/ar}` anchored the substitution to the
end of the path, so a versioned binary like `/usr/bin/llvm-objcopy-21`
(used by CI) did not match and `AR` was left as `llvm-objcopy-21`. The
script then ran `llvm-objcopy-21 x lib.a`, and `llvm-objcopy` treated
`x` as an input file:

    /usr/bin/llvm-objcopy-21: error: 'x': No such file or directory

Substitute `objcopy` only inside the basename and re-attach the
directory, so both `/usr/bin/llvm-objcopy-21` and
`/opt/objcopy-tools/bin/llvm-objcopy` resolve correctly.

CI: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=90043&sha=latest&name_0=PR

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 7bb9509 ("Remove unnecessary dispatch") removed the
`x86_64_v3` specialization on the assumption that at a v3 build the
`Default` namespace is already v3, making the per-function attribute
redundant.

That assumption breaks for files compiled with explicit `-march=x86-64-v2`
overrides. `FunctionsHashingMisc.cpp` is one such file (overridden to
avoid an unrelated SLP regression in `xxh3 ClickHouse#48` / `farmHash64 ClickHouse#5`).
Without a v3-attribute specialization there, the runtime dispatcher
falls back to the v2-compiled `Default` body, which uses SSE2/XMM
codegen instead of AVX2/YMM. That regressed `hiveHash` / `javaHash` on
UUID and Decimal128 columns by 12-18% (perf tests
`general_purpose_hashes_on_UUID ClickHouse#30` and `ClickHouse#32`, `ClickHouse#58` on Decimal).

Restore the `DECLARE_X86_64_V3_SPECIFIC_CODE` macro, the `x86_64_v3`
slot in `DECLARE_MULTITARGET_CODE`, and the per-function `x86_64_v3`
registration in both `FunctionIntHash` and `FunctionAnyHash`. This goes
hand-in-hand with the v2 override on `FunctionsHashingMisc.cpp`: that
override stays as the SLP fix, and this restoration makes the dispatcher
recover the AVX2 fast path on hosts that support it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Algunenano and others added 2 commits May 12, 2026 08:48
…ssion

At v3 the `UniquesHashSet::merge` probe loop lowers `good(hash) = (hash >> s) << s == hash`
to BMI2 `bzhi`/`shlxq`/`shrxq`, which is slightly slower than the legacy `shl %cl + not + test`
sequence on Sapphire Rapids and regresses `uniq_stored #1` by ~9%. Building this single TU with
`-mno-bmi -mno-bmi2` restores the legacy shift codegen while keeping the rest of v3
(AVX2/FMA/etc.) untouched. Bench: v2 0.214s, v3-LTO before 0.233s (+9%), v3-LTO after 0.218s.

The override must live in `src/AggregateFunctions/CMakeLists.txt` because that is where the
`clickhouse_aggregate_functions` target is created. `set_source_files_properties` set in the
parent `src/CMakeLists.txt` scope does not propagate to a target defined in a subdirectory
(unlike the `StorageGenerateRandom` precedent, where the file is absorbed into the `dbms`
target in the same scope via `add_object_library`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Algunenano

Copy link
Copy Markdown
Member Author

@rschu1ze @nickitat The PR is finally ready for review

Some notes:

  • We use vzeroupper which, for calling small functions using AVX2, is bad for performance (more instructions, larger binary, less i-cache hits...). On the other hand, not using it on Intel would show huge degradations in performance because switching between AVX and SSE has large penalties. AMD would be fine, better in fact, without it. I opted out for the lesser evil

  • There were some large regressions in some functions that I was unable to fix with directives or minor code changes. For now we fallback to compiling those files with v2 instead. In the future I'd like to remove them by doing larger refactors, or breaking the files to reduce its scope. I'd rather not do this in this PR, as there are plenty of "minor" changes already.

  • Due to vzeroupper, code size increases, different code layout, etc., there are a bunch of functions where there might be a small degradation. I've addressed all that the performance tests have shown, focusing on anything that isn't flaky in master and showed 10+% change in CI. I've analyzed all of them on a Intel(R) Xeon(R) Platinum 8488C (EC2 C7i instances), matching what the CI runs.

Current performance report: https://pastila.nl/?00da47bb/5185028c8a271967d39877b08c189567#IQymHLWEeKoq+EhoEfv/iQ==GCM

Summary for 11e6269:

  • Improvements: 52 queries (broad arithmetic / date-time / string / aggregation wins).
  • Regressions: 7 flagged → 6 previously discarded as binary-layout/timing/master-flake noise → 1 (uniq_stored #1) fixed by 66f976d53eb.
  • Variance: 50 unstable — mostly known master-flakes or shard wobble; no new actionable signal.
  • *Net real regressions on AMD: 0.

We can keep iterating more, but at this point I think it's safe to review and merge.

@clickhouse-gh

clickhouse-gh Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 84.10% 84.10% +0.00%
Functions 91.10% 92.10% +1.00%
Branches 76.50% 76.50% +0.00%

Changed lines: 95.28% (726/762) | lost baseline coverage: 25 line(s) · Uncovered code

Full report · Diff report

@rschu1ze rschu1ze left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Algunenano I actually reviewed an earlier state of the PR (also approved) and then the incremental commits you pushed since. Looks all good to me. Thanks for this PR!

@Algunenano

Copy link
Copy Markdown
Member Author

@Algunenano Algunenano added pr-backward-incompatible Pull request with backwards incompatible changes and removed pr-performance Pull request with some performance improvements labels May 13, 2026
@Algunenano Algunenano enabled auto-merge May 13, 2026 08:18
@Algunenano Algunenano added this pull request to the merge queue May 13, 2026
Merged via the queue into ClickHouse:master with commit e21a84e May 13, 2026
322 of 326 checks passed
@Algunenano Algunenano deleted the x86v3 branch May 13, 2026 08:52
@robot-clickhouse-ci-2 robot-clickhouse-ci-2 added the pr-synced-to-cloud The PR is synced to the cloud repo label May 13, 2026
Algunenano added a commit to Algunenano/ClickHouse that referenced this pull request May 15, 2026
…6-64-v3 (AVX2)"

This reverts PR ClickHouse#90043 (merge commit e21a84e) due to performance
regressions observed in the MSan build.

Note: a post-merge follow-up commit (9df07df, "Pass nm/ar/objcopy
paths from CMake to localize_rust_c_symbols.sh") added an `NM_PATH`
lookup in `cmake/tools.cmake` for the now-removed
`cmake/localize_rust_c_symbols.sh` helper. The lookup is left in place
by this revert; it is unused but harmless and can be cleaned up
separately.
alexey-milovidov added a commit that referenced this pull request May 19, 2026
PR #90043 set the default x86 build target to x86-64-v3 (AVX2); PR #104971
reverted it in the same release range. Neither change is visible in the
26.5 release, so per the edit-changelog skill rule both entries are
removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-backward-incompatible Pull request with backwards incompatible changes pr-synced-to-cloud The PR is synced to the cloud repo submodule changed At least one submodule changed in this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StringZilla ISA auto-detection mismatch causes build failure with X86_ARCH_LEVEL=4

6 participants