arrs_buffer/
buffer.rs

1use std::{
2    alloc::{alloc_zeroed, dealloc, Layout},
3    ptr::NonNull,
4    sync::Arc,
5};
6
7use crate::{BufferRef, ALIGNMENT};
8
9/// Buffer is a mutable byte container that is aligned to [crate::ALIGNMENT] bytes,
10/// and has a padding so the size of the underlying allocation is a multiple of [crate::ALIGNMENT].
11///
12/// The alignment is because we want it to be aligned to the cacheline. The padding is because we
13/// want it to be easily usable with SIMD instructions without checking length.
14pub struct Buffer {
15    ptr: NonNull<u8>,
16    layout: Layout,
17    len: usize,
18}
19
20impl Buffer {
21    /// Create a new buffer. This function will allocate a padded and aligned memory chunk that
22    ///  is able to hold the given len number of bytes. The allocated memory will be zeroed.
23    ///
24    /// Doesn't allocate if `len` is zero. In this case the buffer will have a dangling pointer.
25    ///
26    /// # Panics
27    ///
28    /// Panics if `len` overflows `isize` when padded to [crate::ALIGNMENT] bytes. Or if memory
29    ///  can't be allocated.
30    pub fn new(len: usize) -> Self {
31        // Don't allocate if len is 0
32        if len == 0 {
33            return Self {
34                ptr: std::ptr::NonNull::dangling(),
35                layout: Layout::from_size_align(64, 64).unwrap(),
36                len,
37            };
38        }
39
40        let padded_len = len.checked_next_multiple_of(ALIGNMENT).unwrap();
41        let layout = Layout::from_size_align(padded_len, ALIGNMENT).unwrap();
42
43        let ptr = unsafe { alloc_zeroed(layout) };
44
45        let ptr = NonNull::new(ptr).unwrap();
46
47        Self { ptr, layout, len }
48    }
49
50    /// Get a pointer to the underlying memory.
51    ///
52    /// The underlying memory is aligned to [crate::ALIGNMENT] bytes and padded to [crate::ALIGNMENT] bytes.
53    pub fn as_ptr(&self) -> *const u8 {
54        self.ptr.as_ptr()
55    }
56
57    /// Get a mutable pointer to the underlying memory.
58    ///
59    /// The underlying memory is aligned to [crate::ALIGNMENT] bytes and padded to [crate::ALIGNMENT] bytes.
60    pub fn as_mut_ptr(&mut self) -> *mut u8 {
61        self.ptr.as_ptr()
62    }
63
64    /// Get a slice to the underlying memory
65    pub fn as_slice(&self) -> &[u8] {
66        unsafe { std::slice::from_raw_parts(self.as_ptr(), self.len) }
67    }
68
69    /// Get a mutable slice to the underlying memory
70    pub fn as_mut_slice(&mut self) -> &mut [u8] {
71        unsafe { std::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }
72    }
73
74    /// Loads data from `src` to the underlying memory. Lenghth of `self` must be greater than or equal to the length of `src`
75    ///
76    /// This might be faster than the regular memcopy, especially for large copies. Because it bypasses the
77    /// CPU cache when writing if possible. This is only advantageus if the user won't be reading the memory
78    ///  stored in `self`, for example it can be good when reading bulk data from disk but not so much if
79    ///  summing small/medium (fits in CPU cache) sized integer arrays and then doing other computation with them.
80    ///
81    /// # Panics
82    ///
83    /// Panics if self.len() < src.len()
84    pub fn cold_load(&mut self, src: &[u8]) {
85        assert!(self.len >= src.len());
86
87        unsafe { crate::cold_load::cold_copy(src.as_ptr(), self.as_mut_ptr(), src.len()) }
88    }
89
90    /// Length of the buffer. Keep in mind that the underlying memory is padded to [crate::ALIGNMENT] bytes
91    /// so might be bigger than the returned value.
92    pub fn len(&self) -> usize {
93        self.len
94    }
95
96    /// Returns if length of buffer is zero
97    pub fn is_empty(&self) -> bool {
98        self.len() == 0
99    }
100
101    /// Create a Buffer from given slice.
102    pub fn from_slice(src: &[u8]) -> Self {
103        // Has to be mut because we write to it with buf.ptr
104        #[allow(unused_mut)]
105        let mut buf = Self::new(src.len());
106
107        unsafe {
108            std::ptr::copy_nonoverlapping(src.as_ptr(), buf.as_mut_ptr(), buf.len);
109        }
110
111        buf
112    }
113
114    /// Similar to [Self::from_slice] but bypasses CPU cache for writes if possible.
115    ///
116    /// See [Self::cold_load] for tradeoffs.
117    pub fn from_slice_cold(src: &[u8]) -> Self {
118        let mut buf = Self::new(src.len());
119        buf.cold_load(src);
120        buf
121    }
122
123    /// Convert this buffer into a reference for zero copy shared usage.
124    pub fn into_ref(self) -> BufferRef {
125        let len = self.len;
126        BufferRef::new(Arc::new(self), 0, len)
127    }
128}
129
130impl Drop for Buffer {
131    fn drop(&mut self) {
132        unsafe {
133            // Don't dealloc if we have 0 len, because we didn't alloc at the start.
134            if self.len > 0 {
135                dealloc(self.as_mut_ptr(), self.layout);
136            }
137        }
138    }
139}
140
141impl Clone for Buffer {
142    fn clone(&self) -> Self {
143        unsafe {
144            // Has to be mut because we write to it with other.ptr
145            #[allow(unused_mut)]
146            let mut other = Self::new(self.len);
147
148            std::ptr::copy_nonoverlapping(self.as_ptr(), other.as_mut_ptr(), self.len);
149            other
150        }
151    }
152}
153
154unsafe impl Send for Buffer {}
155unsafe impl Sync for Buffer {}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn zero_sized() {
163        let _buf = Buffer::new(0);
164    }
165
166    #[test]
167    fn big_sized() {
168        let _buf = Buffer::new(1331);
169    }
170
171    #[test]
172    fn cold_copy() {
173        let src = &[1, 2, 3];
174        let mut buf = Buffer::from_slice_cold(src);
175        assert_eq!(buf.as_slice(), src);
176
177        let src = &[5, 4];
178        buf.cold_load(src);
179        assert_eq!(buf.as_slice(), &[5, 4, 3]);
180
181        let src = (0..244).collect::<Vec<u8>>();
182        let buf = Buffer::from_slice_cold(&src);
183        assert_eq!(buf.as_slice(), src);
184    }
185}