1 //===-- Benchmark memory specific tools -------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 // This file complements the `benchmark` header with memory specific tools and 10 // benchmarking facilities. 11 12 #ifndef LLVM_LIBC_UTILS_BENCHMARK_MEMORY_BENCHMARK_H 13 #define LLVM_LIBC_UTILS_BENCHMARK_MEMORY_BENCHMARK_H 14 15 #include "LibcBenchmark.h" 16 #include "MemorySizeDistributions.h" 17 #include "llvm/ADT/SmallVector.h" 18 #include "llvm/Support/Alignment.h" 19 #include <cstdint> 20 #include <random> 21 22 namespace llvm { 23 namespace libc_benchmarks { 24 25 //-------------- 26 // Configuration 27 //-------------- 28 29 struct StudyConfiguration { 30 // One of 'memcpy', 'memset', 'memcmp'. 31 // The underlying implementation is always the llvm libc one. 32 // e.g. 'memcpy' will test '__llvm_libc::memcpy' 33 std::string Function; 34 35 // The number of trials to run for this benchmark. 36 // If in SweepMode, each individual sizes are measured 'NumTrials' time. 37 // i.e 'NumTrials' measurements for 0, 'NumTrials' measurements for 1 ... 38 uint32_t NumTrials = 1; 39 40 // Toggles between Sweep Mode and Distribution Mode (default). 41 // See 'SweepModeMaxSize' and 'SizeDistributionName' below. 42 bool IsSweepMode = false; 43 44 // Maximum size to use when measuring a ramp of size values (SweepMode). 45 // The benchmark measures all sizes from 0 to SweepModeMaxSize. 46 // Note: in sweep mode the same size is sampled several times in a row this 47 // will allow the processor to learn it and optimize the branching pattern. 48 // The resulting measurement is likely to be idealized. 49 uint32_t SweepModeMaxSize = 0; // inclusive 50 51 // The name of the distribution to be used to randomize the size parameter. 52 // This is used when SweepMode is false (default). 53 std::string SizeDistributionName; 54 55 // This parameter allows to control how the buffers are accessed during 56 // benchmark: 57 // None : Use a fixed address that is at least cache line aligned, 58 // 1 : Use random address, 59 // >1 : Use random address aligned to value. 60 MaybeAlign AccessAlignment = None; 61 62 // When Function == 'memcmp', this is the buffers mismatch position. 63 // 0 : Buffers always compare equal, 64 // >0 : Buffers compare different at byte N-1. 65 uint32_t MemcmpMismatchAt = 0; 66 }; 67 68 struct Runtime { 69 // Details about the Host (cpu name, cpu frequency, cache hierarchy). 70 HostState Host; 71 72 // The framework will populate this value so all data accessed during the 73 // benchmark will stay in L1 data cache. This includes bookkeeping data. 74 uint32_t BufferSize = 0; 75 76 // This is the number of distinct parameters used in a single batch. 77 // The framework always tests a batch of randomized parameter to prevent the 78 // cpu from learning branching patterns. 79 uint32_t BatchParameterCount = 0; 80 81 // The benchmark options that were used to perform the measurement. 82 // This is decided by the framework. 83 BenchmarkOptions BenchmarkOptions; 84 }; 85 86 //-------- 87 // Results 88 //-------- 89 90 // The root object containing all the data (configuration and measurements). 91 struct Study { 92 std::string StudyName; 93 Runtime Runtime; 94 StudyConfiguration Configuration; 95 std::vector<Duration> Measurements; 96 }; 97 98 //------ 99 // Utils 100 //------ 101 102 // Provides an aligned, dynamically allocated buffer. 103 class AlignedBuffer { 104 char *const Buffer = nullptr; 105 size_t Size = 0; 106 107 public: 108 // Note: msan / asan can't handle Alignment > 512. 109 static constexpr size_t Alignment = 512; 110 111 explicit AlignedBuffer(size_t Size) 112 : Buffer(static_cast<char *>(aligned_alloc(Alignment, Size))), 113 Size(Size) {} 114 ~AlignedBuffer() { free(Buffer); } 115 116 inline char *operator+(size_t Index) { return Buffer + Index; } 117 inline const char *operator+(size_t Index) const { return Buffer + Index; } 118 inline char &operator[](size_t Index) { return Buffer[Index]; } 119 inline const char &operator[](size_t Index) const { return Buffer[Index]; } 120 inline char *begin() { return Buffer; } 121 inline char *end() { return Buffer + Size; } 122 }; 123 124 // Helper to generate random buffer offsets that satisfy the configuration 125 // constraints. 126 class OffsetDistribution { 127 std::uniform_int_distribution<uint32_t> Distribution; 128 uint32_t Factor; 129 130 public: 131 explicit OffsetDistribution(size_t BufferSize, size_t MaxSizeValue, 132 MaybeAlign AccessAlignment); 133 134 template <class Generator> uint32_t operator()(Generator &G) { 135 return Distribution(G) * Factor; 136 } 137 }; 138 139 // Helper to generate random buffer offsets that satisfy the configuration 140 // constraints. It is specifically designed to benchmark `memcmp` functions 141 // where we may want the Nth byte to differ. 142 class MismatchOffsetDistribution { 143 std::uniform_int_distribution<size_t> MismatchIndexSelector; 144 llvm::SmallVector<uint32_t, 16> MismatchIndices; 145 const uint32_t MismatchAt; 146 147 public: 148 explicit MismatchOffsetDistribution(size_t BufferSize, size_t MaxSizeValue, 149 size_t MismatchAt); 150 151 explicit operator bool() const { return !MismatchIndices.empty(); } 152 153 const llvm::SmallVectorImpl<uint32_t> &getMismatchIndices() const { 154 return MismatchIndices; 155 } 156 157 template <class Generator> uint32_t operator()(Generator &G, uint32_t Size) { 158 const uint32_t MismatchIndex = MismatchIndices[MismatchIndexSelector(G)]; 159 // We need to position the offset so that a mismatch occurs at MismatchAt. 160 if (Size >= MismatchAt) 161 return MismatchIndex - MismatchAt; 162 // Size is too small to trigger the mismatch. 163 return MismatchIndex - Size - 1; 164 } 165 }; 166 167 /// This structure holds a vector of ParameterType. 168 /// It makes sure that BufferCount x BufferSize Bytes and the vector of 169 /// ParameterType can all fit in the L1 cache. 170 struct ParameterBatch { 171 struct ParameterType { 172 unsigned OffsetBytes : 16; // max : 16 KiB - 1 173 unsigned SizeBytes : 16; // max : 16 KiB - 1 174 }; 175 176 ParameterBatch(size_t BufferCount); 177 178 /// Verifies that memory accessed through this parameter is valid. 179 void checkValid(const ParameterType &) const; 180 181 /// Computes the number of bytes processed during within this batch. 182 size_t getBatchBytes() const; 183 184 const size_t BufferSize; 185 const size_t BatchSize; 186 std::vector<ParameterType> Parameters; 187 }; 188 189 /// Memory function prototype and configuration. 190 using MemcpyFunction = void *(*)(void *__restrict, const void *__restrict, 191 size_t); 192 struct MemcpyConfiguration { 193 MemcpyFunction Function; 194 llvm::StringRef Name; 195 }; 196 197 using MemsetFunction = void *(*)(void *, int, size_t); 198 struct MemsetConfiguration { 199 MemsetFunction Function; 200 llvm::StringRef Name; 201 }; 202 203 using BzeroFunction = void (*)(void *, size_t); 204 struct BzeroConfiguration { 205 BzeroFunction Function; 206 llvm::StringRef Name; 207 }; 208 209 using MemcmpFunction = int (*)(const void *, const void *, size_t); 210 struct MemcmpConfiguration { 211 MemcmpFunction Function; 212 llvm::StringRef Name; 213 }; 214 215 /// Provides source and destination buffers for the Copy operation as well as 216 /// the associated size distributions. 217 struct CopySetup : public ParameterBatch { 218 CopySetup(); 219 220 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 221 return getMemcpySizeDistributions(); 222 } 223 224 inline void *Call(ParameterType Parameter, MemcpyFunction Memcpy) { 225 return Memcpy(DstBuffer + Parameter.OffsetBytes, 226 SrcBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 227 } 228 229 private: 230 AlignedBuffer SrcBuffer; 231 AlignedBuffer DstBuffer; 232 }; 233 234 /// Provides destination buffer for the Set operation as well as the associated 235 /// size distributions. 236 struct SetSetup : public ParameterBatch { 237 SetSetup(); 238 239 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 240 return getMemsetSizeDistributions(); 241 } 242 243 inline void *Call(ParameterType Parameter, MemsetFunction Memset) { 244 return Memset(DstBuffer + Parameter.OffsetBytes, 245 Parameter.OffsetBytes % 0xFF, Parameter.SizeBytes); 246 } 247 248 inline void *Call(ParameterType Parameter, BzeroFunction Bzero) { 249 Bzero(DstBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 250 return DstBuffer.begin(); 251 } 252 253 private: 254 AlignedBuffer DstBuffer; 255 }; 256 257 /// Provides left and right buffers for the Comparison operation as well as the 258 /// associated size distributions. 259 struct ComparisonSetup : public ParameterBatch { 260 ComparisonSetup(); 261 262 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 263 return getMemcmpSizeDistributions(); 264 } 265 266 inline int Call(ParameterType Parameter, MemcmpFunction Memcmp) { 267 return Memcmp(LhsBuffer + Parameter.OffsetBytes, 268 RhsBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 269 } 270 271 private: 272 AlignedBuffer LhsBuffer; 273 AlignedBuffer RhsBuffer; 274 }; 275 276 } // namespace libc_benchmarks 277 } // namespace llvm 278 279 #endif // LLVM_LIBC_UTILS_BENCHMARK_MEMORY_BENCHMARK_H 280