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}