Skip to main content

yo_common/
prefetch.rs

1//! Telling the cache what the next walk is going to want.
2//!
3//! `04` section 3 walks a drained batch twice. The first walk works out which
4//! index bucket each command will land in and asks for that line; the second
5//! walk executes, and by then the line is on its way or already there. The
6//! whole batch is the prefetch distance, which is 64 rather than Valkey's or
7//! Redis 8.4's 16, because Y1 means there is no lock held across the window and
8//! no other thread that can invalidate a bucket between the ask and the use.
9//!
10//! There is no stable portable intrinsic for this, so there are three
11//! implementations here and they are all one instruction. x86_64 gets
12//! `prefetcht0`, aarch64 gets `prfm pldl1keep`, and anything else gets nothing
13//! at all, because a hint that has to be emulated is not a hint. Miri also gets
14//! nothing, since it does not run inline assembly and there is no correctness
15//! in here for it to check.
16
17/// Ask for the cache line at `p`, for reading, into every level of cache.
18///
19/// A hint and only a hint. It cannot fault, it cannot fail, and it does not
20/// change what any later load returns. The only thing it can do wrong is be
21/// pointed at a line nobody wants, which costs bandwidth and nothing else.
22///
23/// The pointer is not dereferenced, so it does not have to be aligned and it
24/// does not have to be readable, but it should point at something real or the
25/// prefetch is just noise.
26#[inline(always)]
27pub fn prefetch_read(p: *const u8) {
28    // Miri does not do inline assembly and has nothing to say about a hint, so
29    // it gets the version where the hint is not there.
30    #[cfg(miri)]
31    let _ = p;
32
33    #[cfg(all(not(miri), target_arch = "x86_64"))]
34    // SAFETY: `_mm_prefetch` is a hint. It does not read through the pointer,
35    // it cannot fault whatever the pointer holds, and it is available on every
36    // x86_64 because SSE is part of the baseline.
37    unsafe {
38        core::arch::x86_64::_mm_prefetch(p.cast::<i8>(), core::arch::x86_64::_MM_HINT_T0);
39    }
40
41    #[cfg(all(not(miri), target_arch = "aarch64"))]
42    // SAFETY: `prfm` is a hint. The architecture defines it as having no effect
43    // other than on performance, it does not fault on an address the load would
44    // have faulted on, and the operand is only read.
45    unsafe {
46        core::arch::asm!(
47            "prfm pldl1keep, [{p}]",
48            p = in(reg) p,
49            options(nostack, readonly, preserves_flags),
50        );
51    }
52
53    #[cfg(all(not(miri), not(any(target_arch = "x86_64", target_arch = "aarch64"))))]
54    let _ = p;
55}
56
57/// The same hint for a reference, which is the shape the call sites have.
58#[inline(always)]
59pub fn prefetch<T>(r: &T) {
60    prefetch_read(core::ptr::from_ref(r).cast::<u8>());
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    /// There is nothing to assert about a hint except that it happened and the
68    /// program carried on, which is exactly what this checks. It is here so
69    /// that a build for an architecture without one of the two instructions
70    /// still compiles and runs this path.
71    #[test]
72    fn a_hint_changes_nothing_it_can_be_asked_about() {
73        let v: Vec<u64> = (0..1024).collect();
74        for i in (0..v.len()).step_by(8) {
75            prefetch(&v[i]);
76        }
77        assert_eq!(v[1023], 1023);
78        prefetch_read(v.as_ptr().cast());
79        assert_eq!(v.iter().sum::<u64>(), (0..1024u64).sum());
80    }
81
82    /// A pointer past the end is still only a hint. This is the case that would
83    /// be a segfault if the implementation ever became a load.
84    #[test]
85    fn a_line_nobody_owns_is_still_only_a_hint() {
86        let v = [1u8, 2, 3, 4];
87        // SAFETY: `add` on the one past the end pointer is in bounds for the
88        // pointer arithmetic rules, and nothing dereferences it here.
89        let past = unsafe { v.as_ptr().add(v.len()) };
90        prefetch_read(past);
91        assert_eq!(v[0], 1);
92    }
93}