Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,17 @@ jobs:
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy

# Builds use `-C target-cpu=native`, so cached artifacts (including proc macros, which run in
# the compiler) only work on runners with the same target features
- name: Identify native target features
id: native
run: |
echo "features=$(rustc --print cfg -C target-cpu=native | grep '^target_feature=' | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"

- name: Caching
uses: Swatinem/rust-cache@v2
with:
key: native-${{ steps.native.outputs.features }}

- name: Check
# clippy will also do a build check
Expand All @@ -48,10 +57,6 @@ jobs:
cargo clippy -p roaring --all-targets --no-default-features -- -D warnings
cargo clippy -p roaring --all-targets --features serde -- -Dclippy::todo -D warnings

- name: Check SIMD
if: matrix.rust == 'nightly'
run: cargo clippy -p roaring --all-targets --all-features -- -D warnings

- name: Check formatting
run: cargo fmt --all -- --check

Expand All @@ -68,9 +73,6 @@ jobs:
features:
- default
- no-std
include:
- rust: nightly
features: simd
env:
RUSTFLAGS: "-C target-cpu=native -C opt-level=3"
ROARINGRS_BENCH_OFFLINE: "true"
Expand All @@ -85,8 +87,17 @@ jobs:
repository: "RoaringBitmap/real-roaring-datasets"
path: "benchmarks/real-roaring-datasets"

# Builds use `-C target-cpu=native`, so cached artifacts (including proc macros, which run in
# the compiler) only work on runners with the same target features
- name: Identify native target features
id: native
run: |
echo "features=$(rustc --print cfg -C target-cpu=native | grep '^target_feature=' | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"

- name: Caching
uses: Swatinem/rust-cache@v2
with:
key: native-${{ steps.native.outputs.features }}

- name: Initialize rust toolchain
uses: dtolnay/rust-toolchain@master
Expand All @@ -105,10 +116,6 @@ jobs:
if: matrix.features == 'no-std'
run: cargo test -p roaring --no-default-features

- name: SIMD test
if: matrix.rust == 'nightly' && matrix.features == 'simd'
run: cargo +nightly test -p roaring --features simd

miri:
runs-on: ubuntu-latest
needs: build
Expand All @@ -126,8 +133,17 @@ jobs:
toolchain: nightly
components: miri

# Builds use `-C target-cpu=native`, so cached artifacts (including proc macros, which run in
# the compiler) only work on runners with the same target features
- name: Identify native target features
id: native
run: |
echo "features=$(rustc --print cfg -C target-cpu=native | grep '^target_feature=' | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"

- name: Caching
uses: Swatinem/rust-cache@v2
with:
key: native-${{ steps.native.outputs.features }}

- name: Setup miri
run: cargo miri setup
Expand All @@ -150,8 +166,17 @@ jobs:
with:
toolchain: nightly

# Builds use `-C target-cpu=native`, so cached artifacts (including proc macros, which run in
# the compiler) only work on runners with the same target features
- name: Identify native target features
id: native
run: |
echo "features=$(rustc --print cfg -C target-cpu=native | grep '^target_feature=' | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"

- name: Caching
uses: Swatinem/rust-cache@v2
with:
key: native-${{ steps.native.outputs.features }}

- name: Install cargo fuzz
run: cargo install cargo-fuzz
Expand All @@ -165,7 +190,4 @@ jobs:
fuzz/corpus

- name: Run Fuzzer vs croaring for 15 minutes
run: cargo fuzz run against_croaring -s none -- -timeout=5 -max_total_time=900

- name: Run Fuzzer (with simd) vs croaring for 15 minutes
run: cargo fuzz run --features=simd against_croaring -- -timeout=5 -max_total_time=900
run: cargo fuzz run against_croaring -- -timeout=5 -max_total_time=900
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ roaring = { path = "roaring" }
bytemuck = "1.25.2"
byteorder = "1.5.0"
criterion = "0.5"
fearless_simd = { version = "1.0.0", default-features = false }
Comment thread
Dr-Emann marked this conversation as resolved.
Resolved
fearless_simd_macros = "0.1.0"
git2 = { version = "0.21", default-features = false }
indicatif = "0.17"
itertools = "0.15"
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ https://github.com/RoaringBitmap/roaring-rs/actions/workflows/test.yml/badge.svg
[real-world datasets]: https://github.com/RoaringBitmap/real-roaring-datasets
[on the user guide]: https://bheisler.github.io/criterion.rs/book/user_guide/user_guide.html

## Experimental features
## SIMD

The `simd` feature is in active development. It has not been tested. If you would like to build with `simd` note that
`std::simd` is only available in Rust nightly.
Some operations are accelerated with SIMD, implemented with the [fearless_simd] crate. When the `std` feature is
enabled, the best available SIMD instruction set is detected at runtime on x86 and x86-64; otherwise, it is determined
by the target features enabled at compile time (e.g. with `-C target-cpu=native`).

[fearless_simd]: https://docs.rs/fearless_simd
3 changes: 0 additions & 3 deletions benchmarks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ indicatif = { workspace = true }
itertools = { workspace = true }
zip = { workspace = true, default-features = false, features = ["deflate"] }

[features]
simd = ["roaring/simd"]

[[bench]]
name = "lib"
harness = false
47 changes: 43 additions & 4 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ libfuzzer-sys = { version = "0.4.9", features = ["arbitrary-derive"] }
roaring = { path = "../roaring" }
croaring = "2.0"

[features]
simd = ["roaring/simd"]

[[bin]]
name = "against_croaring"
path = "fuzz_targets/against_croaring.rs"
Expand Down
6 changes: 4 additions & 2 deletions roaring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ license = "MIT OR Apache-2.0"
bytemuck = { workspace = true, optional = true }
byteorder = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
# At least one of std and libm is required; std overrides libm.
fearless_simd = { workspace = true, features = ["libm"] }
fearless_simd_macros = { workspace = true }

[features]
default = ["std"]
serde = ["dep:serde", "std"]
simd = []
std = ["dep:bytemuck", "dep:byteorder"]
std = ["dep:bytemuck", "dep:byteorder", "fearless_simd/std"]

[dev-dependencies]
proptest = { workspace = true }
Expand Down
51 changes: 6 additions & 45 deletions roaring/src/bitmap/store/array_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,7 @@ impl ArrayStore {

pub fn intersection_len(&self, other: &Self) -> u64 {
let mut visitor = CardinalityCounter::new();
#[cfg(feature = "simd")]
vector::and(self.as_slice(), other.as_slice(), &mut visitor);
#[cfg(not(feature = "simd"))]
scalar::and(self.as_slice(), other.as_slice(), &mut visitor);
visitor.into_inner()
}

Expand Down Expand Up @@ -418,10 +415,7 @@ impl BitOr<Self> for &ArrayStore {
#[allow(clippy::suspicious_arithmetic_impl)]
let capacity = self.vec.len() + rhs.vec.len();
let mut visitor = VecWriter::new(capacity);
#[cfg(feature = "simd")]
vector::or(self.as_slice(), rhs.as_slice(), &mut visitor);
#[cfg(not(feature = "simd"))]
scalar::or(self.as_slice(), rhs.as_slice(), &mut visitor);
ArrayStore::from_vec_unchecked(visitor.into_inner())
}
}
Expand All @@ -431,31 +425,16 @@ impl BitAnd<Self> for &ArrayStore {

fn bitand(self, rhs: Self) -> Self::Output {
let mut visitor = VecWriter::new(self.vec.len().min(rhs.vec.len()));
#[cfg(feature = "simd")]
vector::and(self.as_slice(), rhs.as_slice(), &mut visitor);
#[cfg(not(feature = "simd"))]
scalar::and(self.as_slice(), rhs.as_slice(), &mut visitor);
ArrayStore::from_vec_unchecked(visitor.into_inner())
}
}

impl BitAndAssign<&Self> for ArrayStore {
#[allow(clippy::suspicious_op_assign_impl)]
fn bitand_assign(&mut self, rhs: &Self) {
#[cfg(feature = "simd")]
{
let mut visitor = VecWriter::new(self.vec.len().min(rhs.vec.len()));
vector::and(self.as_slice(), rhs.as_slice(), &mut visitor);
self.vec = visitor.into_inner()
}
#[cfg(not(feature = "simd"))]
{
let mut i = 0;
self.retain(|x| {
i += rhs.iter().skip(i).position(|y| *y >= x).unwrap_or(rhs.vec.len());
rhs.vec.get(i).is_some_and(|y| x == *y)
});
}
let mut visitor = VecWriter::new(self.vec.len().min(rhs.vec.len()));
vector::and(self.as_slice(), rhs.as_slice(), &mut visitor);
self.vec = visitor.into_inner()
}
}

Expand All @@ -470,31 +449,16 @@ impl Sub<Self> for &ArrayStore {

fn sub(self, rhs: Self) -> Self::Output {
let mut visitor = VecWriter::new(self.vec.len());
#[cfg(feature = "simd")]
vector::sub(self.as_slice(), rhs.as_slice(), &mut visitor);
#[cfg(not(feature = "simd"))]
scalar::sub(self.as_slice(), rhs.as_slice(), &mut visitor);
ArrayStore::from_vec_unchecked(visitor.into_inner())
}
}

impl SubAssign<&Self> for ArrayStore {
#[allow(clippy::suspicious_op_assign_impl)]
fn sub_assign(&mut self, rhs: &Self) {
#[cfg(feature = "simd")]
{
let mut visitor = VecWriter::new(self.vec.len().min(rhs.vec.len()));
vector::sub(self.as_slice(), rhs.as_slice(), &mut visitor);
self.vec = visitor.into_inner()
}
#[cfg(not(feature = "simd"))]
{
let mut i = 0;
self.retain(|x| {
i += rhs.iter().skip(i).position(|y| *y >= x).unwrap_or(rhs.vec.len());
rhs.vec.get(i).is_none_or(|y| x != *y)
});
}
let mut visitor = VecWriter::new(self.vec.len().min(rhs.vec.len()));
vector::sub(self.as_slice(), rhs.as_slice(), &mut visitor);
self.vec = visitor.into_inner()
}
}

Expand All @@ -511,10 +475,7 @@ impl BitXor<Self> for &ArrayStore {
#[allow(clippy::suspicious_arithmetic_impl)]
let capacity = self.vec.len() + rhs.vec.len();
let mut visitor = VecWriter::new(capacity);
#[cfg(feature = "simd")]
vector::xor(self.as_slice(), rhs.as_slice(), &mut visitor);
#[cfg(not(feature = "simd"))]
scalar::xor(self.as_slice(), rhs.as_slice(), &mut visitor);
ArrayStore::from_vec_unchecked(visitor.into_inner())
}
}
Expand Down
Loading
Loading