Skip to main content

polars_utils/
mem.rs

1use std::sync::LazyLock;
2
3pub static PAGE_SIZE: LazyLock<usize> = LazyLock::new(|| {
4    #[cfg(target_family = "unix")]
5    unsafe {
6        libc::sysconf(libc::_SC_PAGESIZE) as usize
7    }
8    #[cfg(not(target_family = "unix"))]
9    {
10        4096
11    }
12});
13
14pub mod prefetch {
15    use super::PAGE_SIZE;
16
17    /// # Safety
18    ///
19    /// This should only be called with pointers to valid memory.
20    #[inline]
21    unsafe fn prefetch_l2_impl(ptr: *const u8) {
22        _ = ptr; // Silence unused - not always used on all platforms.
23
24        #[cfg(target_arch = "x86_64")]
25        {
26            use std::arch::x86_64::*;
27            unsafe { _mm_prefetch(ptr as *const _, _MM_HINT_T1) };
28        }
29
30        #[cfg(all(target_arch = "aarch64", feature = "nightly"))]
31        {
32            use std::arch::aarch64::*;
33            unsafe { _prefetch(ptr as *const _, _PREFETCH_READ, _PREFETCH_LOCALITY2) };
34        }
35    }
36
37    /// Prefetch the cache line of `ptr` into the L1 cache. A prefetch never
38    /// faults, so `ptr` may point anywhere.
39    #[inline(always)]
40    pub fn prefetch_l1(ptr: *const u8) {
41        _ = ptr; // Silence unused - not always used on all platforms.
42
43        #[cfg(target_arch = "x86_64")]
44        {
45            use std::arch::x86_64::*;
46            unsafe { _mm_prefetch::<_MM_HINT_T0>(ptr as *const _) };
47        }
48
49        #[cfg(all(target_arch = "aarch64", feature = "nightly"))]
50        {
51            use std::arch::aarch64::*;
52            unsafe { _prefetch(ptr as *const _, _PREFETCH_READ, _PREFETCH_LOCALITY3) };
53        }
54    }
55
56    /// Attempt to prefetch the memory in the slice to the L2 cache.
57    pub fn prefetch_l2(slice: &[u8]) {
58        if slice.is_empty() {
59            return;
60        }
61
62        // @TODO: We can play a bit more with this prefetching. Maybe introduce a maximum number of
63        // prefetches as to not overwhelm the processor. The linear prefetcher should pick it up
64        // at a certain point.
65
66        for i in (0..slice.len()).step_by(*PAGE_SIZE) {
67            unsafe { prefetch_l2_impl(slice[i..].as_ptr()) };
68        }
69
70        unsafe { prefetch_l2_impl(slice[slice.len() - 1..].as_ptr()) }
71    }
72
73    /// `madvise()` with `MADV_SEQUENTIAL` on unix systems. This is a no-op on non-unix systems.
74    pub fn madvise_sequential(#[allow(unused)] slice: &[u8]) {
75        #[cfg(target_family = "unix")]
76        madvise(slice, libc::MADV_SEQUENTIAL);
77    }
78
79    /// `madvise()` with `MADV_WILLNEED` on unix systems. This is a no-op on non-unix systems.
80    pub fn madvise_willneed(#[allow(unused)] slice: &[u8]) {
81        #[cfg(target_family = "unix")]
82        madvise(slice, libc::MADV_WILLNEED);
83    }
84
85    /// `madvise()` with `MADV_POPULATE_READ` on linux systems. This a no-op on non-linux systems.
86    pub fn madvise_populate_read(#[allow(unused)] slice: &[u8]) {
87        #[cfg(target_os = "linux")]
88        madvise(slice, libc::MADV_POPULATE_READ);
89    }
90
91    /// Forcibly reads at least one byte each page.
92    pub fn force_populate_read(slice: &[u8]) {
93        for i in (0..slice.len()).step_by(*PAGE_SIZE) {
94            std::hint::black_box(slice[i]);
95        }
96
97        std::hint::black_box(slice.last().copied());
98    }
99
100    #[cfg(target_family = "unix")]
101    fn madvise(slice: &[u8], advice: libc::c_int) {
102        if slice.is_empty() {
103            return;
104        }
105        let ptr = slice.as_ptr();
106
107        let align = ptr as usize % *PAGE_SIZE;
108        let ptr = ptr.wrapping_sub(align);
109        let len = slice.len() + align;
110
111        if unsafe { libc::madvise(ptr as *mut libc::c_void, len, advice) } != 0 {
112            let err = std::io::Error::last_os_error();
113            if let std::io::ErrorKind::InvalidInput = err.kind() {
114                panic!("{}", err);
115            }
116        }
117    }
118
119    pub fn no_prefetch(_: &[u8]) {}
120
121    /// Get the configured memory prefetch function.
122    pub fn get_memory_prefetch_func(verbose: bool) -> fn(&[u8]) -> () {
123        let memory_prefetch_func = match std::env::var("POLARS_MEMORY_PREFETCH").ok().as_deref() {
124            None => {
125                // madvise_willneed performed the best on both MacOS on Apple Silicon and Ubuntu on x86-64,
126                // using PDS-H query 3 SF=10 after clearing file cache as a benchmark.
127                #[cfg(target_family = "unix")]
128                {
129                    madvise_willneed
130                }
131                #[cfg(not(target_family = "unix"))]
132                {
133                    no_prefetch
134                }
135            },
136            Some("no_prefetch") => no_prefetch,
137            Some("prefetch_l2") => prefetch_l2,
138            Some("madvise_sequential") => {
139                #[cfg(target_family = "unix")]
140                {
141                    madvise_sequential
142                }
143                #[cfg(not(target_family = "unix"))]
144                {
145                    panic!(
146                        "POLARS_MEMORY_PREFETCH=madvise_sequential is not supported by this system"
147                    );
148                }
149            },
150            Some("madvise_willneed") => {
151                #[cfg(target_family = "unix")]
152                {
153                    madvise_willneed
154                }
155                #[cfg(not(target_family = "unix"))]
156                {
157                    panic!(
158                        "POLARS_MEMORY_PREFETCH=madvise_willneed is not supported by this system"
159                    );
160                }
161            },
162            Some("madvise_populate_read") => {
163                #[cfg(target_os = "linux")]
164                {
165                    madvise_populate_read
166                }
167                #[cfg(not(target_os = "linux"))]
168                {
169                    panic!(
170                        "POLARS_MEMORY_PREFETCH=madvise_populate_read is not supported by this system"
171                    );
172                }
173            },
174            Some("force_populate_read") => force_populate_read,
175            Some(v) => panic!("invalid value for POLARS_MEMORY_PREFETCH: {v}"),
176        };
177
178        if verbose {
179            let func_name = match memory_prefetch_func as usize {
180                v if v == no_prefetch as *const () as usize => "no_prefetch",
181                v if v == prefetch_l2 as *const () as usize => "prefetch_l2",
182                v if v == madvise_sequential as *const () as usize => "madvise_sequential",
183                v if v == madvise_willneed as *const () as usize => "madvise_willneed",
184                v if v == madvise_populate_read as *const () as usize => "madvise_populate_read",
185                v if v == force_populate_read as *const () as usize => "force_populate_read",
186                _ => unreachable!(),
187            };
188
189            eprintln!("memory prefetch function: {func_name}");
190        }
191
192        memory_prefetch_func
193    }
194}