Files
Manuel d97c3d2495 Add vectorized batch path for groupBitOr/groupBitAnd/groupBitXor
`AggregateFunctionBitwise` was the only common numeric aggregate without
`addBatchSinglePlace`/`addBatchSinglePlaceNotNull` overrides, so with the
`-If` combinator it fell back to the generic per-row branch-guarded loop,
paying a branch misprediction per row for unpredictable predicates.

Accumulate into a local value and apply the flag as a branchless
all-ones/all-zeros mask (OR: `acc |= x & m`, AND: `acc &= x | ~m`,
XOR: `acc ^= x & m`), writing back to the state once per block. Wide
integers build the mask with `memset`, mirroring the `is_over_big_int`
path of `sum`. Dispatch with `MULTITARGET_FUNCTION_X86_V4`, like `sum`.

Measured (50M rows, 1 thread, unpredictable 50% predicate, server-side
query time): `groupBitOrIf`/`groupBitAndIf`/`groupBitXorIf` -74%,
`UInt128`/`UInt256` -51..-57%, `Nullable` -31%, `Nullable` + `-If` -59%.
No regressions: predictable predicates and the no-`If` path are unchanged.
2026-07-15 00:36:41 +02:00

29 lines
1.6 KiB
XML

<test>
<query>SELECT groupBitOrIf(number, rand32() % 2 = 0) FROM numbers(100000000)</query>
<query>SELECT groupBitAndIf(number, rand32() % 2 = 0) FROM numbers(100000000)</query>
<query>SELECT groupBitXorIf(number, rand32() % 2 = 0) FROM numbers(100000000)</query>
<query>SELECT groupBitOrIf(toUInt32(number), rand32() % 2 = 0) FROM numbers(100000000)</query>
<query>SELECT groupBitOrIf(toUInt8(number), rand32() % 2 = 0) FROM numbers(100000000)</query>
<query>SELECT groupBitOrIf(number::UInt128, rand32() % 2 = 0) FROM numbers(50000000)</query>
<query>SELECT groupBitOrIf(number::UInt256, rand32() % 2 = 0) FROM numbers(50000000)</query>
<query>SELECT groupBitXorIf(number::UInt256, rand32() % 2 = 0) FROM numbers(50000000)</query>
<query>SELECT groupBitOr(number) FROM numbers(100000000)</query>
<query>SELECT groupBitAnd(number) FROM numbers(100000000)</query>
<query>SELECT groupBitXor(number) FROM numbers(100000000)</query>
<!-- ~20% NULL values, random so the branch predictor doesn't do all the work -->
<create_query>CREATE TABLE nullint64 (x Nullable(UInt64)) ENGINE = Memory</create_query>
<fill_query>INSERT INTO nullint64
SELECT IF(rand() % 5 == 0, NULL::Nullable(UInt64), cityHash64(number)) AS x
FROM numbers_mt(200000000)
SETTINGS max_threads = 8
</fill_query>
<query>SELECT groupBitOr(x) FROM nullint64</query>
<query>SELECT groupBitXor(x) FROM nullint64</query>
<query>SELECT groupBitOrIf(x, rand32() % 2 = 0) FROM nullint64</query>
<drop_query>DROP TABLE IF EXISTS nullint64</drop_query>
</test>