mmap-bitvec 0.4.1

A library for working with bit-vectors backed by memory-mapped files
Documentation
extern crate criterion;
extern crate memmap2;
extern crate mmap_bitvec;

use std::fs::OpenOptions;
use std::mem::transmute;
use std::ops::Range;

use memmap2::{MmapMut, MmapOptions};
use mmap_bitvec::{BitVector, MmapBitVec};

use criterion::{criterion_group, criterion_main, Criterion};

type BitVecSlice = u64;

const BIT_VEC_SLICE_SIZE: u8 = 64;
const FILENAME: &str = "./data/bfield.mmap";

fn next_random(n: usize) -> usize {
    // https://en.wikipedia.org/wiki/Xorshift
    let mut x = n as u32;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    x as usize
}

fn get_range_simplified(mmap: &MmapMut, size: usize, l: usize) -> BitVecSlice {
    let byte_idx_st = (l >> 3) as usize;
    let byte_idx_en = ((l + 63) >> 3) as usize;
    let new_size: u8 = 64 as u8;

    let ptr: *const u8 = mmap.as_ptr();

    // read the last byte first
    let end = unsafe { *ptr.offset(byte_idx_en as isize) };
    // align the end of the data with the end of the u64/u128
    let mut v = BitVecSlice::from(end);
    v >>= 7 - ((l + 63) & 7);

    if l < size - BIT_VEC_SLICE_SIZE as usize {
        unsafe {
            let lg_ptr: *const BitVecSlice = transmute(ptr.offset(byte_idx_st as isize));
            v |= (*lg_ptr).to_be() << (l & 7) >> (BIT_VEC_SLICE_SIZE - new_size);
        }
    } else {
        // special case if we can't get a whole u64 out without running outside the buffer
        let bit_offset = new_size + (l & 7) as u8;
        for (new_idx, old_idx) in (byte_idx_st..byte_idx_en).enumerate() {
            unsafe {
                v |= BitVecSlice::from(*ptr.offset(old_idx as isize))
                    << (bit_offset - 8u8 * (new_idx as u8 + 1));
            }
        }
    }

    // mask out the high bits in case we copied extra
    v & (BitVecSlice::max_value() >> (BIT_VEC_SLICE_SIZE - new_size))
}

fn get_range(mmap: &MmapMut, size: usize, r: Range<usize>) -> BitVecSlice {
    if r.end - r.start > BIT_VEC_SLICE_SIZE as usize {
        panic!("Range too large (>{})", BIT_VEC_SLICE_SIZE)
    } else if r.end > size {
        panic!("Range ends outside of BitVec")
    }
    let byte_idx_st = (r.start >> 3) as usize;
    let byte_idx_en = ((r.end - 1) >> 3) as usize;
    let new_size: u8 = (r.end - r.start) as u8;

    let mut v;
    let ptr: *const u8 = mmap.as_ptr();

    // read the last byte first
    unsafe {
        v = BitVecSlice::from(*ptr.offset(byte_idx_en as isize));
    }
    // align the end of the data with the end of the u64/u128
    v >>= 7 - ((r.end - 1) & 7);

    if r.start < size - BIT_VEC_SLICE_SIZE as usize {
        unsafe {
            let lg_ptr: *const BitVecSlice = transmute(ptr.offset(byte_idx_st as isize));
            v |= (*lg_ptr).to_be() << (r.start & 7) >> (BIT_VEC_SLICE_SIZE - new_size);
        }
    } else {
        // special case if we can't get a whole u64 out without running outside the buffer
        let bit_offset = new_size + (r.start & 7) as u8;
        for (new_idx, old_idx) in (byte_idx_st..byte_idx_en).enumerate() {
            unsafe {
                v |= BitVecSlice::from(*ptr.offset(old_idx as isize))
                    << (bit_offset - 8u8 * (new_idx as u8 + 1));
            }
        }
    }

    // mask out the high bits in case we copied extra
    v & (BitVecSlice::max_value() >> (BIT_VEC_SLICE_SIZE - new_size))
}

fn bench_get_range_simplified() {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(FILENAME)
        .unwrap();
    let size = file.metadata().unwrap().len() as usize;
    let mmap = unsafe { MmapOptions::new().map_mut(&file).unwrap() };

    let mut r = 0;
    let mut i = 1;
    for _ in 0..100_000 {
        let l = i % (size - 64);
        r += get_range_simplified(&mmap, size, l).count_ones();
        i = next_random(i);
    }
}

fn bench_get_range() {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(FILENAME)
        .unwrap();
    let size = file.metadata().unwrap().len() as usize;
    let mmap = unsafe { MmapOptions::new().map_mut(&file).unwrap() };

    let mut r = 0;
    let mut i = 1;
    for _ in 0..100_000 {
        let l = i % (size - 64);
        r += get_range(&mmap, size, l..l + 64).count_ones();
        i = next_random(i);
    }
}

fn bench_get_range_actual() {
    let bitvec = MmapBitVec::open_no_header(FILENAME, 0).unwrap();
    let mut r = 0;
    let mut i = 1;
    for _ in 0..100_000 {
        let l = i % (bitvec.size() - 64);
        r += bitvec.get_range(l..l + 64).count_ones();
        i = next_random(i);
    }
}

fn bench_save_to_disk(bv: &MmapBitVec) {
    bv.save_to_disk("hello.tmp", Some([0, 1]), &[]).unwrap();
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("get_range_actual", |b| b.iter(|| bench_get_range_actual()));
    c.bench_function("get_range_simplified", |b| {
        b.iter(|| bench_get_range_simplified())
    });
    c.bench_function("get_range", |b| b.iter(|| bench_get_range()));
    c.bench_function("save_to_disk", |b| {
        let mut bitvec = MmapBitVec::from_memory(100_000_000).unwrap();
        for i in 0..100_000_000 {
            bitvec.set(i, true);
        }
        b.iter(|| bench_save_to_disk(&bitvec))
    });
    // clean up temp file
    std::fs::remove_file("./hello.tmp").unwrap();
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);