sto/
repository.rs

1use crate::arena::Arena;
2use crate::constants::{BUCKET_NUMBER, BUCKET_RSHIFT};
3use crate::entry::{Entries, Entry};
4use ahash::RandomState;
5use parking_lot::Mutex;
6use std::fmt;
7use std::fmt::Formatter;
8use std::hash::{BuildHasher, Hasher};
9
10/// A [Repository] used to store interned strings.
11///
12/// Once a `Repository` is dropped,
13/// all interned strings held by it become unavailable.
14/// This is typically ensured by Rust's lifetime mechanism.
15///
16/// The `Repository` can be safely shared among multiple threads.
17///
18/// To intern a string, see [ScopedSto::intern_in](crate::ScopedSto::intern_in).
19pub struct Repository {
20    buckets: [Bucket; BUCKET_NUMBER],
21}
22
23impl Repository {
24    /// Constructs a new [Repository].
25    ///
26    /// The newly constructed `Repository` does not allocate memory initially.
27    /// Memory allocation only occurs when there is a string to be interned.
28    ///
29    ///
30    ///
31    /// ## Example
32    /// ```
33    /// # use sto::Repository;
34    /// let repository = Repository::new();
35    /// ```
36    pub fn new() -> Self {
37        Self {
38            buckets: [(); BUCKET_NUMBER].map(|_| Bucket::default()),
39        }
40    }
41
42    /// Returns the number of bytes allocated by the [Repository].
43    ///
44    /// ## Example
45    /// ```
46    /// # use sto::{Repository, ScopedSto};
47    /// let repository = Repository::new();
48    /// ScopedSto::intern_in("hello", &repository);
49    ///
50    /// let allocated_memory = repository.allocated_memory();
51    /// println!("Allocated memory: {} bytes", allocated_memory);
52    /// ```
53    pub fn allocated_memory(&self) -> usize {
54        self.buckets
55            .iter()
56            .enumerate()
57            .map(|(_, b)| {
58                let b = b.0.lock();
59                b.entries.allocated_memory() + b.arena.allocated_memory()
60            })
61            .sum()
62    }
63}
64
65impl Repository {
66    pub(crate) fn get_or_insert(&self, string: &str) -> Entry {
67        let hash = Self::get_hash(string);
68        self.buckets[Self::determine_bucket(hash)]
69            .0
70            .lock()
71            .get_or_insert(hash, string)
72    }
73}
74
75impl Repository {
76    fn get_hash(string: &str) -> u64 {
77        static RANDOM: RandomState =
78            RandomState::with_seeds(0x01230456, 0x04560789, 0x07890123, 0x02580137);
79        let mut hasher = RANDOM.build_hasher();
80        hasher.write(string.as_bytes());
81        hasher.finish()
82    }
83
84    const fn determine_bucket(hash: u64) -> usize {
85        (hash >> BUCKET_RSHIFT) as usize
86    }
87}
88
89impl Default for Repository {
90    /// See [Repository::new].
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl fmt::Debug for Repository {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        f.debug_struct("Repository").finish()
99    }
100}
101
102#[repr(align(32))]
103#[derive(Default)]
104struct Bucket(Mutex<BucketImpl>);
105
106/// BucketImpl has 32 bytes on 64 bit hardware
107#[derive(Default)]
108struct BucketImpl {
109    arena: Arena,
110    entries: Entries,
111}
112
113impl BucketImpl {
114    #[inline]
115    fn get_or_insert(&mut self, hash: u64, string: &str) -> Entry {
116        self.entries
117            .get_or_insert(hash, string, || Entry(self.arena.alloc_str(hash, string)))
118    }
119}