// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // // Benchmarks for int.trailingZeroBitCount and int.oneBitCount, exercised // through a small bit-array implementation. Each operation has an // `Accelerated` variant (HW intrinsic for counting operations, Int32x4 SIMD // for word-level bitwise operations) and a `Swar` baseline that does the // same work without HW acceleration, so the speedup attributable to the // hardware path is visible directly. // // `main()` first runs a correctness check that asserts both // implementations agree across a range of sizes and densities, then // reports benchmark timings via the standard BenchmarkBase harness. import 'dart:math'; import 'dart:typed_data'; import 'package:benchmark_harness/benchmark_harness.dart'; // 32-bit storage words so the benchmark runs on both native (64-bit int) // and web (32-bit bitwise) without platform-specific word arithmetic. const int _wordBits = 32; const int _wordMask = _wordBits - 1; const int _wordShift = 5; class BitArray { final Uint32List _words; final int length; BitArray(this.length) : _words = Uint32List((length + _wordBits - 1) >> _wordShift); Uint32List get words => _words; void setBit(int i) { _words[i >> _wordShift] |= 1 << (i & _wordMask); } // ----- cardinality (popcount across all words) --------------------------- // Accelerated: hardware popcount via int.oneBitCount. int cardinalityAccelerated() { var total = 0; for (var i = 0; i < _words.length; i++) { total += _words[i].oneBitCount; } return total; } int cardinalitySwar() { var total = 0; for (var i = 0; i < _words.length; i++) { total += _popcountWord(_words[i]); } return total; } // ----- forEachSetBit (iterate set bits via ctz + clear-lowest) ----------- // Accelerated: int.trailingZeroBitCount per surviving bit. void forEachSetBitAccelerated(void Function(int) action) { for (var wordIdx = 0; wordIdx < _words.length; wordIdx++) { var w = _words[wordIdx]; final base = wordIdx << _wordShift; while (w != 0) { final bit = w.trailingZeroBitCount; final pos = base + bit; if (pos >= length) return; action(pos); // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan w &= w - 1; } } } void forEachSetBitSwar(void Function(int) action) { for (var wordIdx = 0; wordIdx < _words.length; wordIdx++) { var w = _words[wordIdx]; final base = wordIdx << _wordShift; while (w != 0) { final bit = _ctzWord(w); final pos = base + bit; if (pos >= length) return; action(pos); // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan w &= w - 1; } } } // ----- select(k) (position of the k-th set bit, k >= 0) ----------------- // Accelerated: int.oneBitCount to skip whole words, then walk the // target word with int.trailingZeroBitCount. int selectAccelerated(int k) { var remaining = k; for (var wordIdx = 0; wordIdx < _words.length; wordIdx++) { final w = _words[wordIdx]; final pop = w.oneBitCount; if (remaining < pop) { var v = w; while (remaining > 0) { // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan v &= v - 1; remaining--; } return (wordIdx << _wordShift) + v.trailingZeroBitCount; } remaining -= pop; } return -1; } // Fair non-accelerated baseline: SWAR popcount to skip whole words, // SWAR ctz inside the target word. int selectSwar(int k) { var remaining = k; for (var wordIdx = 0; wordIdx < _words.length; wordIdx++) { final w = _words[wordIdx]; final pop = _popcountWord(w); if (remaining < pop) { var v = w; while (remaining > 0) { // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan v &= v - 1; remaining--; } return (wordIdx << _wordShift) + _ctzWord(v); } remaining -= pop; } return -1; } // ----- totalBitLength (sum of int.bitLength across every word) ---------- // // A synthetic operation that exercises int.bitLength once per word, the // way cardinality exercises int.oneBitCount once per word. // Accelerated: int.bitLength (asm-intrinsic CLZ today). int totalBitLengthAccelerated() { var total = 0; for (var i = 0; i < _words.length; i++) { total += _words[i].bitLength; } return total; } // Software bit-trick equivalent of bitLength on a 32-bit word. int totalBitLengthSwar() { var total = 0; for (var i = 0; i < _words.length; i++) { final v = _words[i]; if (v != 0) total += _highBitInWord(v) + 1; } return total; } // ----- complementCardinality (popcount of bitwise NOT each word) ------- // // Exercises int.~ once per word, immediately followed by oneBitCount. // Accelerated: int.oneBitCount on each complemented word. int complementCardinalityAccelerated() { var total = 0; for (var i = 0; i < _words.length; i++) { // Mask back to 32 bits because ~uint32 sign-extends in Dart's int. total += (~_words[i] & 0xFFFFFFFF).oneBitCount; } return total; } // Fair non-accelerated baseline: SWAR popcount on each complemented word. int complementCardinalitySwar() { var total = 0; for (var i = 0; i < _words.length; i++) { total += _popcountWord(~_words[i] & 0xFFFFFFFF); } return total; } // ----- intersection (out = a AND b, materialized as a new BitArray) ----- // Word-level scalar AND. static void intersectionSwar(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; for (var i = 0; i < n; i++) { wo[i] = wa[i] & wb[i]; } } // Accelerated: 4 words per Int32x4 vector op. Tail uses scalar. static void intersectionAccelerated(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; final simdWords = n >> 2; final va = Int32x4List.view(wa.buffer, wa.offsetInBytes, simdWords); final vb = Int32x4List.view(wb.buffer, wb.offsetInBytes, simdWords); final vo = Int32x4List.view(wo.buffer, wo.offsetInBytes, simdWords); for (var i = 0; i < simdWords; i++) { vo[i] = va[i] & vb[i]; } for (var i = simdWords << 2; i < n; i++) { wo[i] = wa[i] & wb[i]; } } // ----- union (out = a OR b) ------------------------------------------- static void unionSwar(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; for (var i = 0; i < n; i++) { wo[i] = wa[i] | wb[i]; } } static void unionAccelerated(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; final simdWords = n >> 2; final va = Int32x4List.view(wa.buffer, wa.offsetInBytes, simdWords); final vb = Int32x4List.view(wb.buffer, wb.offsetInBytes, simdWords); final vo = Int32x4List.view(wo.buffer, wo.offsetInBytes, simdWords); for (var i = 0; i < simdWords; i++) { vo[i] = va[i] | vb[i]; } for (var i = simdWords << 2; i < n; i++) { wo[i] = wa[i] | wb[i]; } } // ----- xor (out = a XOR b, symmetric difference) ----------------------- static void xorSwar(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; for (var i = 0; i < n; i++) { wo[i] = wa[i] ^ wb[i]; } } static void xorAccelerated(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; final simdWords = n >> 2; final va = Int32x4List.view(wa.buffer, wa.offsetInBytes, simdWords); final vb = Int32x4List.view(wb.buffer, wb.offsetInBytes, simdWords); final vo = Int32x4List.view(wo.buffer, wo.offsetInBytes, simdWords); for (var i = 0; i < simdWords; i++) { vo[i] = va[i] ^ vb[i]; } for (var i = simdWords << 2; i < n; i++) { wo[i] = wa[i] ^ wb[i]; } } // ----- difference (out = a AND NOT b) ---------------------------------- // // Set difference: bits set in `a` and not in `b`. Implemented as // a & ~b at the word / SIMD level. static void differenceSwar(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; for (var i = 0; i < n; i++) { wo[i] = wa[i] & (~wb[i] & 0xFFFFFFFF); } } static void differenceAccelerated(BitArray a, BitArray b, BitArray out) { final wa = a._words; final wb = b._words; final wo = out._words; if (wa.length != wb.length || wa.length != wo.length) { throw ArgumentError('a, b, and out must have the same length'); } final n = wa.length; final simdWords = n >> 2; final va = Int32x4List.view(wa.buffer, wa.offsetInBytes, simdWords); final vb = Int32x4List.view(wb.buffer, wb.offsetInBytes, simdWords); final vo = Int32x4List.view(wo.buffer, wo.offsetInBytes, simdWords); // Int32x4 has no andNot; build via a & ~b (bitwise negate is a single // NEON EOR-with-all-ones on ARM64). final ones = Int32x4(-1, -1, -1, -1); for (var i = 0; i < simdWords; i++) { vo[i] = va[i] & (vb[i] ^ ones); } for (var i = simdWords << 2; i < n; i++) { wo[i] = wa[i] & (~wb[i] & 0xFFFFFFFF); } } // ----- complement (out = NOT a) ---------------------------------------- static void complementSwar(BitArray a, BitArray out) { final wa = a._words; final wo = out._words; if (wa.length != wo.length) { throw ArgumentError('a and out must have the same length'); } final n = wa.length; for (var i = 0; i < n; i++) { wo[i] = ~wa[i] & 0xFFFFFFFF; } } static void complementAccelerated(BitArray a, BitArray out) { final wa = a._words; final wo = out._words; if (wa.length != wo.length) { throw ArgumentError('a and out must have the same length'); } final n = wa.length; final simdWords = n >> 2; final va = Int32x4List.view(wa.buffer, wa.offsetInBytes, simdWords); final vo = Int32x4List.view(wo.buffer, wo.offsetInBytes, simdWords); // No SIMD NOT; emit XOR-with-all-ones, which the VM lowers to a single // NEON EOR on ARM64. final ones = Int32x4(-1, -1, -1, -1); for (var i = 0; i < simdWords; i++) { vo[i] = va[i] ^ ones; } for (var i = simdWords << 2; i < n; i++) { wo[i] = ~wa[i] & 0xFFFFFFFF; } } bool wordsEqual(BitArray other) { if (_words.length != other._words.length) return false; for (var i = 0; i < _words.length; i++) { if (_words[i] != other._words[i]) return false; } return true; } // ----- helpers ---------------------------------------------------------- // SWAR popcount on a 32-bit word; non-accelerated equivalent of // int.oneBitCount. static int _popcountWord(int v) { v = v - ((v >> 1) & 0x55555555); v = (v & 0x33333333) + ((v >> 2) & 0x33333333); v = (v + (v >> 4)) & 0x0F0F0F0F; return ((v * 0x01010101) >> 24) & 0xFF; } // SWAR count-trailing-zeros on a 32-bit word; non-accelerated equivalent // of int.trailingZeroBitCount. Returns 32 for zero. static int _ctzWord(int v) { if (v == 0) return _wordBits; var n = 0; if ((v & 0xFFFF) == 0) { n += 16; v >>= 16; } if ((v & 0xFF) == 0) { n += 8; v >>= 8; } if ((v & 0xF) == 0) { n += 4; v >>= 4; } if ((v & 0x3) == 0) { n += 2; v >>= 2; } if ((v & 0x1) == 0) { n += 1; } return n; } // SWAR log2 on a 32-bit word; non-accelerated equivalent of // int.bitLength minus one (for nonzero v). static int _highBitInWord(int v) { var r = 0; if (v >= 0x10000) { v >>= 16; r += 16; } if (v >= 0x100) { v >>= 8; r += 8; } if (v >= 0x10) { v >>= 4; r += 4; } if (v >= 0x4) { v >>= 2; r += 2; } if (v >= 0x2) { r += 1; } return r; } } // ----- Correctness check ---------------------------------------------------- BitArray _randomArray(int size, Random rng, int densityPercent) { final bits = BitArray(size); for (var i = 0; i < size; i++) { if (rng.nextInt(100) < densityPercent) bits.setBit(i); } return bits; } void _assertEq(Object? actual, Object? expected, String label) { if (actual != expected) { throw StateError('FAIL $label: expected $expected, got $actual'); } } void checkCorrectness() { final rng = Random(0xDA27); for (final size in const [0, 1, 31, 32, 33, 63, 64, 65, 127, 1000, 50000]) { for (final density in const [0, 3, 50, 97, 100]) { final bits = _randomArray(size, rng, density); // Cardinality operations must agree. SWAR is the oracle. final card = bits.cardinalitySwar(); _assertEq( bits.cardinalityAccelerated(), card, 'cardinality.accelerated(size=$size, density=$density)', ); // forEachSetBit: accelerated and swar must enumerate the same // bits in the same order. final acc = []; final swar = []; bits.forEachSetBitAccelerated(acc.add); bits.forEachSetBitSwar(swar.add); _assertEq( acc.length, swar.length, 'forEach count (size=$size, density=$density)', ); for (var i = 0; i < acc.length; i++) { _assertEq( acc[i], swar[i], 'forEach[$i] (size=$size, density=$density)', ); } // select(k): accelerated and swar must agree. for (final k in [0, 1, card >> 1, card - 1]) { if (k < 0 || k >= card) continue; _assertEq( bits.selectAccelerated(k), bits.selectSwar(k), 'select(size=$size, density=$density, k=$k)', ); } _assertEq( bits.complementCardinalityAccelerated(), bits.complementCardinalitySwar(), 'complementCardinality(size=$size, density=$density)', ); _assertEq( bits.totalBitLengthAccelerated(), bits.totalBitLengthSwar(), 'totalBitLength(size=$size, density=$density)', ); // Bitwise pair ops: scalar and SIMD must produce identical results. final other = _randomArray(size, rng, density); final label = 'size=$size, density=$density'; final iSwar = BitArray(size); final iAcc = BitArray(size); BitArray.intersectionSwar(bits, other, iSwar); BitArray.intersectionAccelerated(bits, other, iAcc); _assertEq(iAcc.wordsEqual(iSwar), true, 'intersection($label)'); final uSwar = BitArray(size); final uAcc = BitArray(size); BitArray.unionSwar(bits, other, uSwar); BitArray.unionAccelerated(bits, other, uAcc); _assertEq(uAcc.wordsEqual(uSwar), true, 'union($label)'); final xSwar = BitArray(size); final xAcc = BitArray(size); BitArray.xorSwar(bits, other, xSwar); BitArray.xorAccelerated(bits, other, xAcc); _assertEq(xAcc.wordsEqual(xSwar), true, 'xor($label)'); final dSwar = BitArray(size); final dAcc = BitArray(size); BitArray.differenceSwar(bits, other, dSwar); BitArray.differenceAccelerated(bits, other, dAcc); _assertEq(dAcc.wordsEqual(dSwar), true, 'difference($label)'); // Complement: scalar and SIMD must produce the same bit-flipped array. final cSwar = BitArray(size); final cAcc = BitArray(size); BitArray.complementSwar(bits, cSwar); BitArray.complementAccelerated(bits, cAcc); _assertEq(cAcc.wordsEqual(cSwar), true, 'complement($label)'); } } } // ----- Benchmarks ----------------------------------------------------------- const int _benchSize = 1 << 20; // 1,048,576 bits = 32,768 words. class _BitArrayBenchmark extends BenchmarkBase { final int densityPercent; final void Function(BitArray) operation; late BitArray bits; _BitArrayBenchmark(String name, this.densityPercent, this.operation) : super('BitArray.$name'); @override void setup() { bits = _randomArray(_benchSize, Random(0xBEEF), densityPercent); } @override void run() { operation(bits); } } class _BitArrayPairBenchmark extends BenchmarkBase { final int densityPercent; final void Function(BitArray, BitArray, BitArray) operation; late BitArray a; late BitArray b; late BitArray out; _BitArrayPairBenchmark(String name, this.densityPercent, this.operation) : super('BitArray.$name'); @override void setup() { a = _randomArray(_benchSize, Random(0xBEEF), densityPercent); b = _randomArray(_benchSize, Random(0xC0DE), densityPercent); out = BitArray(_benchSize); } @override void run() { operation(a, b, out); } } List _benchmarks() { // Sinks the optimizer cannot fold away. var sink = 0; void accumulate(int x) { sink ^= x; } return [ // Cardinality: SWAR popcount vs int.oneBitCount. _BitArrayBenchmark( 'cardinality.swar', 25, (bits) => sink ^= bits.cardinalitySwar(), ), _BitArrayBenchmark( 'cardinality.accelerated', 25, (bits) => sink ^= bits.cardinalityAccelerated(), ), // forEachSetBit: Brian-Kernighan loop with SWAR ctz vs int.trailingZeroBitCount. _BitArrayBenchmark( 'forEachSetBit.swar', 25, (bits) => bits.forEachSetBitSwar(accumulate), ), _BitArrayBenchmark( 'forEachSetBit.accelerated', 25, (bits) => bits.forEachSetBitAccelerated(accumulate), ), // select(k) for k near the middle of a quarter-full array. _BitArrayBenchmark( 'select.swar', 25, (bits) => sink ^= bits.selectSwar(_benchSize >> 3), ), _BitArrayBenchmark( 'select.accelerated', 25, (bits) => sink ^= bits.selectAccelerated(_benchSize >> 3), ), // complementCardinality: popcount of (~w & 0xFFFFFFFF) per word. _BitArrayBenchmark( 'complementCardinality.swar', 25, (bits) => sink ^= bits.complementCardinalitySwar(), ), _BitArrayBenchmark( 'complementCardinality.accelerated', 25, (bits) => sink ^= bits.complementCardinalityAccelerated(), ), // totalBitLength: sum int.bitLength across every word. _BitArrayBenchmark( 'totalBitLength.swar', 25, (bits) => sink ^= bits.totalBitLengthSwar(), ), _BitArrayBenchmark( 'totalBitLength.accelerated', 25, (bits) => sink ^= bits.totalBitLengthAccelerated(), ), // Bitwise pair ops: scalar word-level vs Int32x4 SIMD. _BitArrayPairBenchmark('intersection.swar', 25, BitArray.intersectionSwar), _BitArrayPairBenchmark( 'intersection.accelerated', 25, BitArray.intersectionAccelerated, ), _BitArrayPairBenchmark('union.swar', 25, BitArray.unionSwar), _BitArrayPairBenchmark('union.accelerated', 25, BitArray.unionAccelerated), _BitArrayPairBenchmark('xor.swar', 25, BitArray.xorSwar), _BitArrayPairBenchmark('xor.accelerated', 25, BitArray.xorAccelerated), _BitArrayPairBenchmark('difference.swar', 25, BitArray.differenceSwar), _BitArrayPairBenchmark( 'difference.accelerated', 25, BitArray.differenceAccelerated, ), // Complement: scalar ~ vs Int32x4 XOR-with-all-ones. Ignores the // second input of the pair-benchmark harness. _BitArrayPairBenchmark( 'complement.swar', 25, (a, _, out) => BitArray.complementSwar(a, out), ), _BitArrayPairBenchmark( 'complement.accelerated', 25, (a, _, out) => BitArray.complementAccelerated(a, out), ), ]; } void main() { checkCorrectness(); for (final benchmark in _benchmarks()) { benchmark.report(); } }