Loading...
Searching...
No Matches
prefetch.h
Go to the documentation of this file.
1//
2// Copyright 2026 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_BASE_ARCH_PREFETCH_H
8#define PXR_BASE_ARCH_PREFETCH_H
9
17
18#include "pxr/pxr.h"
19
20#include "pxr/base/arch/align.h"
21#include "pxr/base/arch/defines.h"
22
23#include <cstddef>
24#include <cstdint>
25
26#if defined(ARCH_COMPILER_MSVC)
27#include <intrin.h>
28#endif
29
30PXR_NAMESPACE_OPEN_SCOPE
31
34
39 Read = 0,
40 Write = 1
41};
42
47 None = 0,
48 L3 = 1,
49 L2 = 2,
50 L1 = 3
51};
52
53// Prefetch the single cache line containing addr. Implementation helper for
54// ArchPrefetch() and ArchPrefetchRange().
55template <ArchPrefetchAccess Access, ArchPrefetchLocality Locality>
56void Arch_PrefetchOneLine(void const *addr) noexcept
57{
58#if defined(ARCH_COMPILER_GCC) || \
59 defined(ARCH_COMPILER_CLANG) || \
60 defined(ARCH_COMPILER_ICC)
61
62 // The enum values are crafted to match __builtin_prefetch's expectations.
63 // The casts are required: clang demands integer constants here and will not
64 // convert the enumerators implicitly.
65 __builtin_prefetch(addr, static_cast<int>(Access),
66 static_cast<int>(Locality));
67
68#elif defined(ARCH_COMPILER_MSVC) && defined(ARCH_CPU_ARM)
69
70 __prefetch(addr); // No locality/rw options for arm.
71
72#elif defined(ARCH_COMPILER_MSVC) && defined(ARCH_CPU_INTEL)
73
74 constexpr int hint =
75 Locality == ArchPrefetchLocality::None ? _MM_HINT_NTA :
76 Locality == ArchPrefetchLocality::L3 ? _MM_HINT_T2 :
77 Locality == ArchPrefetchLocality::L2 ? _MM_HINT_T1 : _MM_HINT_T0;
78 // No rw option for msvc.
79 _mm_prefetch(reinterpret_cast<char const *>(addr), hint);
80
81#else
82 // Unsupported, do nothing.
83 (void)addr;
84#endif
85}
86
87// Invoke fn with the address of each cache line that the byte range [addr,
88// addr+Size) occupies, in increasing address order. This is factored out of
89// ArchPrefetch() so that tests can observe the line selection, which is
90// otherwise unobservable. It costs nothing: fn inlines away, leaving codegen
91// identical to prefetching inline.
92template <size_t Size, size_t Align, class Fn>
93void
94Arch_ForEachPrefetchLine(void const *addr, Fn &&fn) noexcept
95{
96 constexpr size_t LineSize = ARCH_CACHE_LINE_SIZE;
97
98 static_assert(Align != 0 && (Align & (Align - 1)) == 0,
99 "Align must be a power of two");
100 static_assert((LineSize & (LineSize - 1)) == 0,
101 "ARCH_CACHE_LINE_SIZE must be a power of two");
102
103 constexpr size_t MinLines = (Size + LineSize - 1) / LineSize;
104 constexpr uintptr_t LineMask = ~static_cast<uintptr_t>(LineSize - 1);
105
106 // The worst-case starting offset within a cache line, given alignment
107 // Align, is (ARCH_CACHE_LINE_SIZE - Align) -- the last aligned position
108 // before a line boundary. A straddle into an extra line is only possible
109 // if that worst-case layout pushes the final byte past MinLines lines.
110 // Note an empty range never occupies a line, straddling or otherwise.
111 constexpr bool MightStraddle =
112 Size != 0 && (Align < LineSize) &&
113 ((LineSize - Align) + Size > MinLines * LineSize);
114
115 char const *ptr = reinterpret_cast<char const *>(
116 reinterpret_cast<uintptr_t>(addr) & LineMask);
117
118 // The compiler should unroll this loop of constexpr iterations.
119 for (size_t i = 0; i != MinLines; ++i) {
120 fn(ptr);
121 ptr += LineSize;
122 }
123
124 // If addr + Size spills to an additional line, prefetch it.
125 if constexpr (MightStraddle) {
126 if (reinterpret_cast<char const *>(addr) + Size > ptr) {
127 fn(ptr);
128 }
129 }
130}
131
132// As above, for a length that is only known at runtime. Deriving the line
133// count from the first and last byte's lines is uniform -- there is no straddle
134// case to special-case, and so no use for an alignment guarantee.
135//
136// Requires that `[addr, addr+numBytes)` not wrap the address space, which holds
137// for any real object.
138template <class Fn>
139void
140Arch_ForEachPrefetchLine(void const *addr, size_t numBytes, Fn &&fn) noexcept
141{
142 constexpr size_t LineSize = ARCH_CACHE_LINE_SIZE;
143 constexpr uintptr_t LineMask = ~static_cast<uintptr_t>(LineSize - 1);
144
145 if (numBytes == 0) {
146 return;
147 }
148
149 uintptr_t const a = reinterpret_cast<uintptr_t>(addr);
150 uintptr_t const begin = a & LineMask;
151 uintptr_t const last = (a + numBytes - 1) & LineMask;
152
153 // One line past the last. A range ending in the topmost cache line wraps
154 // this to zero, but `p` wraps to zero on the same step -- both walk aligned
155 // addresses from `begin` -- so the `!=` test still terminates.
156 //
157 // Walking to a sentinel rather than counting iterations matters: given a
158 // trip count, clang unrolls this loop eightfold with a remainder loop,
159 // which is a lot of code to inline at every call site that prefetches a
160 // runtime-length range.
161 uintptr_t const end = last + LineSize;
162
163 uintptr_t p = begin;
164 do {
165 fn(reinterpret_cast<char const *>(p));
166 p += LineSize;
167 } while (p != end);
168}
169
170// As above, for `count` objects of type T. Overload resolution sends a typed
171// pointer here and an untyped one to the overload above, so that a count is
172// always in the units the pointer implies.
173template <class T, class Fn>
174void
175Arch_ForEachPrefetchLine(T const *addr, size_t count, Fn &&fn) noexcept
176{
177 Arch_ForEachPrefetchLine(static_cast<void const *>(addr),
178 count * sizeof(T), fn);
179}
180
188template <size_t Size = 1,
189 size_t Align = 1,
190 ArchPrefetchAccess Access = ArchPrefetchAccess::Read,
191 ArchPrefetchLocality Locality = ArchPrefetchLocality::L2>
192void
193ArchPrefetch(void const *addr) noexcept
194{
195 Arch_ForEachPrefetchLine<Size, Align>(addr, [](void const *line) {
196 Arch_PrefetchOneLine<Access, Locality>(line);
197 });
198}
199
207template <ArchPrefetchAccess Access = ArchPrefetchAccess::Read,
208 ArchPrefetchLocality Locality = ArchPrefetchLocality::L2>
209void
210ArchPrefetchRange(void const *addr, size_t numBytes) noexcept
211{
212 Arch_ForEachPrefetchLine(addr, numBytes, [](void const *line) {
213 Arch_PrefetchOneLine<Access, Locality>(line);
214 });
215}
216
227template <ArchPrefetchAccess Access = ArchPrefetchAccess::Read,
228 ArchPrefetchLocality Locality = ArchPrefetchLocality::L2,
229 class T>
230void
231ArchPrefetchRange(T const *addr, size_t count) noexcept
232{
233 Arch_ForEachPrefetchLine(addr, count, [](void const *line) {
234 Arch_PrefetchOneLine<Access, Locality>(line);
235 });
236}
237
241template <ArchPrefetchLocality Locality = ArchPrefetchLocality::L2, class T>
242void
243ArchPrefetchRead(T const *addr) noexcept
244{
245 ArchPrefetch<sizeof(T), alignof(T),
246 ArchPrefetchAccess::Read, Locality>(addr);
247}
248
252template <ArchPrefetchLocality Locality = ArchPrefetchLocality::L2, class T>
253void
254ArchPrefetchWrite(T const *addr) noexcept
255{
256 ArchPrefetch<sizeof(T), alignof(T),
257 ArchPrefetchAccess::Write, Locality>(addr);
258}
259
261
262PXR_NAMESPACE_CLOSE_SCOPE
263
264#endif // PXR_BASE_ARCH_PREFETCH_H
Provide architecture-specific memory-alignment information.
#define ARCH_CACHE_LINE_SIZE
The size of a CPU cache line on the current processor architecture in bytes.
Definition align.h:67
void ArchPrefetchRead(T const *addr) noexcept
Prefetch for reading the object at addr with given locality.
Definition prefetch.h:243
ArchPrefetchLocality
How much temporal locality you expect on the address after the fetch.
Definition prefetch.h:46
ArchPrefetchAccess
The kind of access a prefetch is preparing for.
Definition prefetch.h:38
void ArchPrefetchRange(void const *addr, size_t numBytes) noexcept
Prefetch the cache lines that [addr, addr+numBytes) occupy, where numBytes is a runtime value.
Definition prefetch.h:210
void ArchPrefetchWrite(T const *addr) noexcept
Prefetch for writing the object at addr with given locality.
Definition prefetch.h:254
void ArchPrefetch(void const *addr) noexcept
Prefetch the cache lines that [addr, addr+Size) occupy.
Definition prefetch.h:193