Skip to main content

moloch_core/
aligned.rs

1//! Cache-line aligned types for high-performance operations.
2//!
3//! Modern CPUs have 64-byte cache lines. Proper alignment prevents:
4//! - False sharing in concurrent code
5//! - Cache line splits during loads/stores
6//! - Suboptimal prefetching
7//!
8//! # Usage
9//!
10//! ```rust
11//! use moloch_core::aligned::{AlignedHash, AlignedHashArray};
12//! use moloch_core::Hash;
13//!
14//! // Single aligned hash (for hot paths)
15//! let some_hash = Hash::ZERO;
16//! let hash = AlignedHash::from(some_hash);
17//!
18//! // Batch of aligned hashes (for SIMD operations)
19//! let hashes = [Hash::ZERO; 4];
20//! let mut batch = AlignedHashArray::<8>::default();
21//! for (i, h) in hashes.iter().enumerate() {
22//!     batch.set(i, *h);
23//! }
24//! ```
25
26use std::ops::{Deref, DerefMut};
27
28use crate::crypto::Hash;
29
30/// Cache line size on most modern CPUs (Intel, AMD, ARM).
31pub const CACHE_LINE_SIZE: usize = 64;
32
33/// A hash aligned to a cache line boundary.
34///
35/// This prevents false sharing when multiple threads access adjacent hashes
36/// and ensures optimal cache behavior for frequently accessed hashes.
37#[repr(C, align(64))]
38#[derive(Clone, Copy, PartialEq, Eq)]
39pub struct AlignedHash {
40    hash: Hash,
41    _padding: [u8; 32], // Pad to full 64-byte cache line
42}
43
44impl AlignedHash {
45    /// Create a new aligned hash.
46    #[inline]
47    pub const fn new(hash: Hash) -> Self {
48        Self {
49            hash,
50            _padding: [0; 32],
51        }
52    }
53
54    /// Create a zero-initialized aligned hash.
55    #[inline]
56    pub const fn zero() -> Self {
57        Self::new(Hash::ZERO)
58    }
59
60    /// Get the underlying hash.
61    #[inline]
62    pub const fn inner(&self) -> &Hash {
63        &self.hash
64    }
65
66    /// Get a mutable reference to the underlying hash.
67    #[inline]
68    pub fn inner_mut(&mut self) -> &mut Hash {
69        &mut self.hash
70    }
71
72    /// Consume and return the inner hash.
73    #[inline]
74    pub fn into_inner(self) -> Hash {
75        self.hash
76    }
77}
78
79impl From<Hash> for AlignedHash {
80    #[inline]
81    fn from(hash: Hash) -> Self {
82        Self::new(hash)
83    }
84}
85
86impl From<AlignedHash> for Hash {
87    #[inline]
88    fn from(aligned: AlignedHash) -> Self {
89        aligned.hash
90    }
91}
92
93impl Deref for AlignedHash {
94    type Target = Hash;
95
96    #[inline]
97    fn deref(&self) -> &Self::Target {
98        &self.hash
99    }
100}
101
102impl DerefMut for AlignedHash {
103    #[inline]
104    fn deref_mut(&mut self) -> &mut Self::Target {
105        &mut self.hash
106    }
107}
108
109impl std::fmt::Debug for AlignedHash {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.debug_tuple("AlignedHash").field(&self.hash).finish()
112    }
113}
114
115/// An array of hashes aligned to cache line boundaries.
116///
117/// Used for batch operations where hashes are processed together.
118/// The entire array is aligned, and hashes are packed contiguously
119/// for optimal SIMD access patterns.
120#[repr(C, align(64))]
121#[derive(Clone)]
122pub struct AlignedHashArray<const N: usize> {
123    hashes: [Hash; N],
124}
125
126impl<const N: usize> AlignedHashArray<N> {
127    /// Create a new array with zero hashes.
128    #[inline]
129    pub const fn new() -> Self {
130        Self {
131            hashes: [Hash::ZERO; N],
132        }
133    }
134
135    /// Create from an existing array.
136    #[inline]
137    pub const fn from_array(hashes: [Hash; N]) -> Self {
138        Self { hashes }
139    }
140
141    /// Get a hash at the specified index.
142    #[inline]
143    pub fn get(&self, index: usize) -> Option<&Hash> {
144        self.hashes.get(index)
145    }
146
147    /// Set a hash at the specified index.
148    #[inline]
149    pub fn set(&mut self, index: usize, hash: Hash) {
150        if index < N {
151            self.hashes[index] = hash;
152        }
153    }
154
155    /// Get the underlying array.
156    #[inline]
157    pub const fn as_array(&self) -> &[Hash; N] {
158        &self.hashes
159    }
160
161    /// Get a mutable reference to the underlying array.
162    #[inline]
163    pub fn as_array_mut(&mut self) -> &mut [Hash; N] {
164        &mut self.hashes
165    }
166
167    /// Get a pointer to the first hash (for SIMD operations).
168    #[inline]
169    pub fn as_ptr(&self) -> *const Hash {
170        self.hashes.as_ptr()
171    }
172
173    /// Get a mutable pointer to the first hash.
174    #[inline]
175    pub fn as_mut_ptr(&mut self) -> *mut Hash {
176        self.hashes.as_mut_ptr()
177    }
178
179    /// Get the raw bytes pointer (for SIMD loads).
180    #[inline]
181    pub fn as_bytes_ptr(&self) -> *const u8 {
182        self.hashes.as_ptr() as *const u8
183    }
184
185    /// Get a slice of all hashes.
186    #[inline]
187    pub fn as_slice(&self) -> &[Hash] {
188        &self.hashes
189    }
190
191    /// Get a mutable slice of all hashes.
192    #[inline]
193    pub fn as_mut_slice(&mut self) -> &mut [Hash] {
194        &mut self.hashes
195    }
196
197    /// Iterate over hashes.
198    #[inline]
199    pub fn iter(&self) -> impl Iterator<Item = &Hash> {
200        self.hashes.iter()
201    }
202
203    /// Iterate over hashes mutably.
204    #[inline]
205    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Hash> {
206        self.hashes.iter_mut()
207    }
208}
209
210impl<const N: usize> Default for AlignedHashArray<N> {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216impl<const N: usize> Deref for AlignedHashArray<N> {
217    type Target = [Hash; N];
218
219    #[inline]
220    fn deref(&self) -> &Self::Target {
221        &self.hashes
222    }
223}
224
225impl<const N: usize> DerefMut for AlignedHashArray<N> {
226    #[inline]
227    fn deref_mut(&mut self) -> &mut Self::Target {
228        &mut self.hashes
229    }
230}
231
232impl<const N: usize> std::fmt::Debug for AlignedHashArray<N> {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        f.debug_struct("AlignedHashArray")
235            .field("len", &N)
236            .field("hashes", &self.hashes)
237            .finish()
238    }
239}
240
241/// A cache-line padded wrapper to prevent false sharing.
242///
243/// Useful for counters, state variables, or any data that is
244/// frequently updated by a single thread but may be adjacent
245/// to data accessed by other threads.
246#[repr(C, align(64))]
247#[derive(Clone, Copy)]
248pub struct CacheLinePadded<T> {
249    value: T,
250}
251
252impl<T> CacheLinePadded<T> {
253    /// Create a new padded value.
254    #[inline]
255    pub const fn new(value: T) -> Self {
256        Self { value }
257    }
258
259    /// Get a reference to the inner value.
260    #[inline]
261    pub const fn inner(&self) -> &T {
262        &self.value
263    }
264
265    /// Get a mutable reference to the inner value.
266    #[inline]
267    pub fn inner_mut(&mut self) -> &mut T {
268        &mut self.value
269    }
270
271    /// Consume and return the inner value.
272    #[inline]
273    pub fn into_inner(self) -> T {
274        self.value
275    }
276}
277
278impl<T> Deref for CacheLinePadded<T> {
279    type Target = T;
280
281    #[inline]
282    fn deref(&self) -> &Self::Target {
283        &self.value
284    }
285}
286
287impl<T> DerefMut for CacheLinePadded<T> {
288    #[inline]
289    fn deref_mut(&mut self) -> &mut Self::Target {
290        &mut self.value
291    }
292}
293
294impl<T: Default> Default for CacheLinePadded<T> {
295    fn default() -> Self {
296        Self::new(T::default())
297    }
298}
299
300impl<T: std::fmt::Debug> std::fmt::Debug for CacheLinePadded<T> {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        f.debug_tuple("CacheLinePadded").field(&self.value).finish()
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use crate::hash;
310
311    #[test]
312    fn test_aligned_hash_size_and_alignment() {
313        assert_eq!(std::mem::size_of::<AlignedHash>(), 64);
314        assert_eq!(std::mem::align_of::<AlignedHash>(), 64);
315    }
316
317    #[test]
318    fn test_aligned_hash_array_alignment() {
319        assert_eq!(std::mem::align_of::<AlignedHashArray<8>>(), 64);
320
321        // Size should be 8 * 32 = 256 bytes (no extra padding needed)
322        assert_eq!(std::mem::size_of::<AlignedHashArray<8>>(), 256);
323    }
324
325    #[test]
326    fn test_cache_line_padded_alignment() {
327        assert_eq!(std::mem::align_of::<CacheLinePadded<u64>>(), 64);
328
329        // Should be padded to at least 64 bytes
330        assert!(std::mem::size_of::<CacheLinePadded<u64>>() >= 64);
331    }
332
333    #[test]
334    fn test_aligned_hash_operations() {
335        let h = hash(b"test");
336        let aligned = AlignedHash::new(h);
337
338        assert_eq!(*aligned.inner(), h);
339        assert_eq!(aligned.into_inner(), h);
340    }
341
342    #[test]
343    fn test_aligned_hash_array_operations() {
344        let mut arr = AlignedHashArray::<4>::new();
345
346        let h0 = hash(b"zero");
347        let h1 = hash(b"one");
348        let h2 = hash(b"two");
349        let h3 = hash(b"three");
350
351        arr.set(0, h0);
352        arr.set(1, h1);
353        arr.set(2, h2);
354        arr.set(3, h3);
355
356        assert_eq!(arr.get(0), Some(&h0));
357        assert_eq!(arr.get(1), Some(&h1));
358        assert_eq!(arr.get(2), Some(&h2));
359        assert_eq!(arr.get(3), Some(&h3));
360        assert_eq!(arr.get(4), None);
361    }
362
363    #[test]
364    fn test_alignment_is_correct_at_runtime() {
365        let aligned = AlignedHash::new(hash(b"test"));
366        let ptr = &aligned as *const AlignedHash as usize;
367        assert_eq!(ptr % 64, 0, "AlignedHash should be 64-byte aligned");
368
369        let arr = AlignedHashArray::<8>::new();
370        let arr_ptr = &arr as *const AlignedHashArray<8> as usize;
371        assert_eq!(
372            arr_ptr % 64,
373            0,
374            "AlignedHashArray should be 64-byte aligned"
375        );
376    }
377
378    #[test]
379    fn test_heap_allocation_alignment() {
380        // Box should maintain alignment
381        let boxed = Box::new(AlignedHash::new(hash(b"test")));
382        let ptr = boxed.as_ref() as *const AlignedHash as usize;
383        assert_eq!(ptr % 64, 0, "Boxed AlignedHash should be 64-byte aligned");
384
385        // Vec should also maintain alignment
386        let mut vec = Vec::with_capacity(4);
387        for i in 0..4u8 {
388            vec.push(AlignedHash::new(hash(&[i])));
389        }
390        for (i, aligned) in vec.iter().enumerate() {
391            let ptr = aligned as *const AlignedHash as usize;
392            assert_eq!(ptr % 64, 0, "Vec element {} should be 64-byte aligned", i);
393        }
394    }
395}