[benchmarks] Add BitArray benchmark
Adds a benchmark exercising int.oneBitCount and int.trailingZeroBitCount (landed in CL 498041) and a representative set of Int32x4 SIMD bitwise kernels, through a small bit-array implementation built on a Uint32List word store. Kernels: - cardinality: popcount across the array. - forEachSetBit: Brian-Kernighan iteration via ctz. - select(k): position of the k-th set bit using popcount + ctz. - complementCardinality: popcount of bitwise-NOT per word. - totalBitLength: sum of int.bitLength per word. - intersection / union / xor / difference: pair-op bitwise kernels using Int32x4 SIMD with a scalar tail. - complement: single-input bitwise NOT via Int32x4 XOR-with-all-ones. Each kernel has a Swar baseline using portable bit-twiddle code and an Accelerated variant that uses the new int getters or Int32x4 SIMD, so the speedup attributable to the hardware/intrinsic path is visible directly. main() first runs a correctness check that asserts both implementations agree across a representative range of sizes and bit densities, then reports timings via the standard BenchmarkBase harness. Work towards https://github.com/dart-lang/sdk/issues/1053 (efficient BitSet implementation). Bug: https://github.com/dart-lang/sdk/issues/52673 Change-Id: I5ae5bc0b9d07f6de3e11907ac1fae80f02e77119 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503020 Commit-Queue: Slava Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Auto-Submit: Modestas Valauskas <valauskasmodestas@gmail.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
181f581345
commit
ace4bb1ad6
@@ -0,0 +1,700 @@
|
||||
// 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 = <int>[];
|
||||
final swar = <int>[];
|
||||
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<BenchmarkBase> _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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user