lasso 0.7.3

A multithreaded and single threaded string interner that allows strings to be cached with a minimal memory footprint, associating them with a unique key that can be used to retrieve them at any time.
Documentation
#![allow(dead_code)]

use lasso::{Capacity, Rodeo, RodeoReader, RodeoResolver, Spur};
use std::{
    collections::hash_map::RandomState,
    hash::BuildHasher,
    num::NonZeroUsize,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, Barrier,
    },
    thread,
    time::{Duration, Instant},
};

pub const NUM_THREADS: usize = 24;

pub static INPUT: &str = include_str!("input.txt");

lazy_static::lazy_static! {
    pub static ref INPUT_LINES: Vec<&'static str> =
        INPUT.split_whitespace().collect::<Vec<&str>>();
}

pub fn bench_lines() -> &'static [&'static str] {
    &INPUT_LINES
}

pub struct RodeoEmptySetup<S: BuildHasher + Clone> {
    lines: &'static [&'static str],
    build_hasher: S,
}

impl<S: BuildHasher + Clone> RodeoEmptySetup<S> {
    pub fn new(build_hasher: S) -> Self {
        let lines = bench_lines();

        Self {
            lines,
            build_hasher,
        }
    }

    pub fn empty_rodeo(&self) -> Rodeo<Spur, S> {
        Rodeo::with_capacity_and_hasher(Capacity::default(), self.build_hasher.clone())
    }

    pub fn lines(&self) -> &'static [&'static str] {
        self.lines
    }
}

pub struct RodeoFilledSetup<S: BuildHasher + Clone> {
    lines: &'static [&'static str],
    rodeo: Rodeo<Spur, S>,
    keys: Vec<Spur>,
}

impl<S: BuildHasher + Clone> RodeoFilledSetup<S> {
    pub fn new(hash_builder: S) -> Self {
        let lines = bench_lines();
        let mut rodeo = Rodeo::with_capacity_and_hasher(
            Capacity::new(
                lines.len(),
                NonZeroUsize::new(lines.iter().map(|l| l.as_bytes().len()).sum()).unwrap(),
            ),
            hash_builder,
        );
        let keys = lines
            .iter()
            .map(|&line| rodeo.get_or_intern(line))
            .collect::<Vec<_>>();

        Self { lines, rodeo, keys }
    }

    pub fn lines(&self) -> &'static [&'static str] {
        self.lines
    }

    pub fn filled_rodeo(&self) -> &Rodeo<Spur, S> {
        &self.rodeo
    }

    pub fn filled_rodeo_mut(&mut self) -> &mut Rodeo<Spur, S> {
        &mut self.rodeo
    }

    pub fn keys(&self) -> &[Spur] {
        &self.keys
    }
}

pub struct ReaderEmptySetup<S: BuildHasher + Clone> {
    lines: &'static [&'static str],
    build_hasher: S,
}

impl<S: BuildHasher + Clone> ReaderEmptySetup<S> {
    pub fn new(build_hasher: S) -> Self {
        let lines = bench_lines();

        Self {
            lines,
            build_hasher,
        }
    }

    pub fn empty_rodeo(&self) -> RodeoReader<Spur, S> {
        Rodeo::with_capacity_and_hasher(Capacity::default(), self.build_hasher.clone())
            .into_reader()
    }

    pub fn lines(&self) -> &'static [&'static str] {
        self.lines
    }
}

pub struct ReaderFilledSetup<S: BuildHasher + Clone> {
    lines: &'static [&'static str],
    reader: RodeoReader<Spur, S>,
    keys: Vec<Spur>,
}

impl<S: BuildHasher + Clone> ReaderFilledSetup<S> {
    pub fn new(hash_builder: S) -> Self {
        let lines = bench_lines();
        let mut rodeo = Rodeo::with_capacity_and_hasher(
            Capacity::new(
                lines.len(),
                NonZeroUsize::new(lines.iter().map(|l| l.as_bytes().len()).sum()).unwrap(),
            ),
            hash_builder,
        );
        let keys = lines
            .iter()
            .map(|&line| rodeo.get_or_intern(line))
            .collect::<Vec<_>>();
        let reader = rodeo.into_reader();

        Self {
            lines,
            reader,
            keys,
        }
    }

    pub fn into_inner(self) -> RodeoReader<Spur, S> {
        self.reader
    }

    pub fn lines(&self) -> &'static [&'static str] {
        self.lines
    }

    pub fn filled_rodeo(&self) -> &RodeoReader<Spur, S> {
        &self.reader
    }

    pub fn filled_rodeo_mut(&mut self) -> &mut RodeoReader<Spur, S> {
        &mut self.reader
    }

    pub fn keys(&self) -> &[Spur] {
        &self.keys
    }
}

pub fn run_reader_filled<F, S>(func: F, hash: S, num_threads: usize, iters: u64) -> Duration
where
    F: FnOnce(&RodeoReader<Spur, S>, &[Spur]) + Send + 'static + Clone + Copy,
    S: 'static + BuildHasher + Clone + Send + Sync,
{
    let setup = ReaderFilledSetup::new(hash);
    let keys = setup.keys().to_vec();
    let reader = Arc::new(setup.into_inner());
    let barrier = Arc::new(Barrier::new(num_threads));
    let mut threads = Vec::with_capacity(num_threads - 1);
    let running = Arc::new(AtomicBool::new(true));

    for _ in 0..num_threads - 1 {
        let barrier = barrier.clone();
        let reader = reader.clone();
        let running = running.clone();
        let keys = keys.clone();

        threads.push(thread::spawn(move || {
            let reader: &RodeoReader<Spur, S> = &reader;
            barrier.wait();
            while running.load(Ordering::Relaxed) {
                func(reader, &keys)
            }
        }));
    }

    let reader: &RodeoReader<Spur, S> = &reader;
    barrier.wait();
    let start = Instant::now();
    for _ in 0..iters {
        func(reader, &keys);
    }
    let time = start.elapsed();

    running.store(false, Ordering::Relaxed);
    threads.into_iter().for_each(|x| x.join().unwrap());

    time
}

pub fn run_resolver_filled<F>(func: F, num_threads: usize, iters: u64) -> Duration
where
    F: FnOnce(&RodeoResolver<Spur>, &[Spur]) + Send + 'static + Clone + Copy,
{
    let setup = ResolverFilledSetup::new();
    let keys = setup.keys().to_vec();
    let reader = Arc::new(setup.into_inner());
    let barrier = Arc::new(Barrier::new(num_threads));
    let mut threads = Vec::with_capacity(num_threads - 1);
    let running = Arc::new(AtomicBool::new(true));

    for _ in 0..num_threads - 1 {
        let barrier = barrier.clone();
        let reader = reader.clone();
        let running = running.clone();
        let keys = keys.clone();

        threads.push(thread::spawn(move || {
            let reader: &RodeoResolver<Spur> = &reader;
            barrier.wait();
            while running.load(Ordering::Relaxed) {
                func(reader, &keys)
            }
        }));
    }

    let reader: &RodeoResolver<Spur> = &reader;
    barrier.wait();
    let start = Instant::now();
    for _ in 0..iters {
        func(reader, &keys);
    }
    let time = start.elapsed();

    running.store(false, Ordering::Relaxed);
    threads.into_iter().for_each(|x| x.join().unwrap());

    time
}

pub struct ResolverFilledSetup {
    lines: &'static [&'static str],
    resolver: RodeoResolver<Spur>,
    keys: Vec<Spur>,
}

impl ResolverFilledSetup {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        let lines = bench_lines();
        let mut rodeo = Rodeo::with_capacity_and_hasher(
            Capacity::new(
                lines.len(),
                NonZeroUsize::new(lines.iter().map(|l| l.as_bytes().len()).sum()).unwrap(),
            ),
            RandomState::new(),
        );
        let keys = lines
            .iter()
            .map(|&line| rodeo.get_or_intern(line))
            .collect::<Vec<_>>();
        let resolver = rodeo.into_resolver();

        Self {
            lines,
            resolver,
            keys,
        }
    }

    pub fn into_inner(self) -> RodeoResolver<Spur> {
        self.resolver
    }

    pub fn lines(&self) -> &'static [&'static str] {
        self.lines
    }

    pub fn filled_rodeo(&self) -> &RodeoResolver<Spur> {
        &self.resolver
    }

    pub fn filled_rodeo_mut(&mut self) -> &mut RodeoResolver<Spur> {
        &mut self.resolver
    }

    pub fn keys(&self) -> &[Spur] {
        &self.keys
    }
}