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 static constexpr size_t Alignment = 1024; 109 110 explicit AlignedBuffer(size_t Size) 111 : Buffer(static_cast<char *>(aligned_alloc(Alignment, Size))), 112 Size(Size) {} 113 ~AlignedBuffer() { free(Buffer); } 114 115 inline char *operator+(size_t Index) { return Buffer + Index; } 116 inline const char *operator+(size_t Index) const { return Buffer + Index; } 117 inline char &operator[](size_t Index) { return Buffer[Index]; } 118 inline const char &operator[](size_t Index) const { return Buffer[Index]; } 119 inline char *begin() { return Buffer; } 120 inline char *end() { return Buffer + Size; } 121 }; 122 123 // Helper to generate random buffer offsets that satisfy the configuration 124 // constraints. 125 class OffsetDistribution { 126 std::uniform_int_distribution<uint32_t> Distribution; 127 uint32_t Factor; 128 129 public: 130 explicit OffsetDistribution(size_t BufferSize, size_t MaxSizeValue, 131 MaybeAlign AccessAlignment); 132 133 template <class Generator> uint32_t operator()(Generator &G) { 134 return Distribution(G) * Factor; 135 } 136 }; 137 138 // Helper to generate random buffer offsets that satisfy the configuration 139 // constraints. It is specifically designed to benchmark `memcmp` functions 140 // where we may want the Nth byte to differ. 141 class MismatchOffsetDistribution { 142 std::uniform_int_distribution<size_t> MismatchIndexSelector; 143 llvm::SmallVector<uint32_t, 16> MismatchIndices; 144 const uint32_t MismatchAt; 145 146 public: 147 explicit MismatchOffsetDistribution(size_t BufferSize, size_t MaxSizeValue, 148 size_t MismatchAt); 149 150 explicit operator bool() const { return !MismatchIndices.empty(); } 151 152 const llvm::SmallVectorImpl<uint32_t> &getMismatchIndices() const { 153 return MismatchIndices; 154 } 155 156 template <class Generator> uint32_t operator()(Generator &G, uint32_t Size) { 157 const uint32_t MismatchIndex = MismatchIndices[MismatchIndexSelector(G)]; 158 // We need to position the offset so that a mismatch occurs at MismatchAt. 159 if (Size >= MismatchAt) 160 return MismatchIndex - MismatchAt; 161 // Size is too small to trigger the mismatch. 162 return MismatchIndex - Size - 1; 163 } 164 }; 165 166 /// This structure holds a vector of ParameterType. 167 /// It makes sure that BufferCount x BufferSize Bytes and the vector of 168 /// ParameterType can all fit in the L1 cache. 169 struct ParameterBatch { 170 struct ParameterType { 171 unsigned OffsetBytes : 16; // max : 16 KiB - 1 172 unsigned SizeBytes : 16; // max : 16 KiB - 1 173 }; 174 175 ParameterBatch(size_t BufferCount); 176 177 /// Verifies that memory accessed through this parameter is valid. 178 void checkValid(const ParameterType &) const; 179 180 /// Computes the number of bytes processed during within this batch. 181 size_t getBatchBytes() const; 182 183 const size_t BufferSize; 184 const size_t BatchSize; 185 std::vector<ParameterType> Parameters; 186 }; 187 188 /// Memory function prototype and configuration. 189 using MemcpyFunction = void *(*)(void *__restrict, const void *__restrict, 190 size_t); 191 struct MemcpyConfiguration { 192 MemcpyFunction Function; 193 llvm::StringRef Name; 194 }; 195 196 using MemsetFunction = void *(*)(void *, int, size_t); 197 struct MemsetConfiguration { 198 MemsetFunction Function; 199 llvm::StringRef Name; 200 }; 201 202 using BzeroFunction = void (*)(void *, size_t); 203 struct BzeroConfiguration { 204 BzeroFunction Function; 205 llvm::StringRef Name; 206 }; 207 208 using MemcmpFunction = int (*)(const void *, const void *, size_t); 209 struct MemcmpConfiguration { 210 MemcmpFunction Function; 211 llvm::StringRef Name; 212 }; 213 214 /// Provides source and destination buffers for the Copy operation as well as 215 /// the associated size distributions. 216 struct CopySetup : public ParameterBatch { 217 CopySetup(); 218 219 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 220 return getMemcpySizeDistributions(); 221 } 222 223 inline void *Call(ParameterType Parameter, MemcpyFunction Memcpy) { 224 return Memcpy(DstBuffer + Parameter.OffsetBytes, 225 SrcBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 226 } 227 228 private: 229 AlignedBuffer SrcBuffer; 230 AlignedBuffer DstBuffer; 231 }; 232 233 /// Provides destination buffer for the Set operation as well as the associated 234 /// size distributions. 235 struct SetSetup : public ParameterBatch { 236 SetSetup(); 237 238 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 239 return getMemsetSizeDistributions(); 240 } 241 242 inline void *Call(ParameterType Parameter, MemsetFunction Memset) { 243 return Memset(DstBuffer + Parameter.OffsetBytes, 244 Parameter.OffsetBytes % 0xFF, Parameter.SizeBytes); 245 } 246 247 inline void *Call(ParameterType Parameter, BzeroFunction Bzero) { 248 Bzero(DstBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 249 return DstBuffer.begin(); 250 } 251 252 private: 253 AlignedBuffer DstBuffer; 254 }; 255 256 /// Provides left and right buffers for the Comparison operation as well as the 257 /// associated size distributions. 258 struct ComparisonSetup : public ParameterBatch { 259 ComparisonSetup(); 260 261 inline static const ArrayRef<MemorySizeDistribution> getDistributions() { 262 return getMemcmpSizeDistributions(); 263 } 264 265 inline int Call(ParameterType Parameter, MemcmpFunction Memcmp) { 266 return Memcmp(LhsBuffer + Parameter.OffsetBytes, 267 RhsBuffer + Parameter.OffsetBytes, Parameter.SizeBytes); 268 } 269 270 private: 271 AlignedBuffer LhsBuffer; 272 AlignedBuffer RhsBuffer; 273 }; 274 275 } // namespace libc_benchmarks 276 } // namespace llvm 277 278 #endif // LLVM_LIBC_UTILS_BENCHMARK_MEMORY_BENCHMARK_H 279