1 2 #include <cstdint> 3 #include <new> 4 #include <vector> 5 6 #include "CartesianBenchmarks.hpp" 7 #include "GenerateInput.hpp" 8 #include "benchmark/benchmark.h" 9 #include "test_macros.h" 10 11 constexpr std::size_t MAX_STRING_LEN = 8 << 14; 12 13 // Benchmark when there is no match. 14 static void BM_StringFindNoMatch(benchmark::State &state) { 15 std::string s1(state.range(0), '-'); 16 std::string s2(8, '*'); 17 for (auto _ : state) 18 benchmark::DoNotOptimize(s1.find(s2)); 19 } 20 BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN); 21 22 // Benchmark when the string matches first time. 23 static void BM_StringFindAllMatch(benchmark::State &state) { 24 std::string s1(MAX_STRING_LEN, '-'); 25 std::string s2(state.range(0), '-'); 26 for (auto _ : state) 27 benchmark::DoNotOptimize(s1.find(s2)); 28 } 29 BENCHMARK(BM_StringFindAllMatch)->Range(1, MAX_STRING_LEN); 30 31 // Benchmark when the string matches somewhere in the end. 32 static void BM_StringFindMatch1(benchmark::State &state) { 33 std::string s1(MAX_STRING_LEN / 2, '*'); 34 s1 += std::string(state.range(0), '-'); 35 std::string s2(state.range(0), '-'); 36 for (auto _ : state) 37 benchmark::DoNotOptimize(s1.find(s2)); 38 } 39 BENCHMARK(BM_StringFindMatch1)->Range(1, MAX_STRING_LEN / 4); 40 41 // Benchmark when the string matches somewhere from middle to the end. 42 static void BM_StringFindMatch2(benchmark::State &state) { 43 std::string s1(MAX_STRING_LEN / 2, '*'); 44 s1 += std::string(state.range(0), '-'); 45 s1 += std::string(state.range(0), '*'); 46 std::string s2(state.range(0), '-'); 47 for (auto _ : state) 48 benchmark::DoNotOptimize(s1.find(s2)); 49 } 50 BENCHMARK(BM_StringFindMatch2)->Range(1, MAX_STRING_LEN / 4); 51 52 static void BM_StringCtorDefault(benchmark::State &state) { 53 for (auto _ : state) { 54 std::string Default; 55 benchmark::DoNotOptimize(Default); 56 } 57 } 58 BENCHMARK(BM_StringCtorDefault); 59 60 enum class Length { Empty, Small, Large, Huge }; 61 struct AllLengths : EnumValuesAsTuple<AllLengths, Length, 4> { 62 static constexpr const char* Names[] = {"Empty", "Small", "Large", "Huge"}; 63 }; 64 65 enum class Opacity { Opaque, Transparent }; 66 struct AllOpacity : EnumValuesAsTuple<AllOpacity, Opacity, 2> { 67 static constexpr const char* Names[] = {"Opaque", "Transparent"}; 68 }; 69 70 enum class DiffType { Control, ChangeFirst, ChangeMiddle, ChangeLast }; 71 struct AllDiffTypes : EnumValuesAsTuple<AllDiffTypes, DiffType, 4> { 72 static constexpr const char* Names[] = {"Control", "ChangeFirst", 73 "ChangeMiddle", "ChangeLast"}; 74 }; 75 76 TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) { 77 switch (D) { 78 case DiffType::Control: 79 return "0123456"; 80 case DiffType::ChangeFirst: 81 return "-123456"; 82 case DiffType::ChangeMiddle: 83 return "012-456"; 84 case DiffType::ChangeLast: 85 return "012345-"; 86 } 87 } 88 89 TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) { 90 #define LARGE_STRING_FIRST "123456789012345678901234567890" 91 #define LARGE_STRING_SECOND "234567890123456789012345678901" 92 switch (D) { 93 case DiffType::Control: 94 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 95 case DiffType::ChangeFirst: 96 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 97 case DiffType::ChangeMiddle: 98 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2"; 99 case DiffType::ChangeLast: 100 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-"; 101 } 102 } 103 104 TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) { 105 #define HUGE_STRING0 "0123456789" 106 #define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 107 #define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 108 #define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 109 #define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 110 switch (D) { 111 case DiffType::Control: 112 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 113 case DiffType::ChangeFirst: 114 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 115 case DiffType::ChangeMiddle: 116 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789"; 117 case DiffType::ChangeLast: 118 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-"; 119 } 120 } 121 122 TEST_ALWAYS_INLINE std::string makeString(Length L, 123 DiffType D = DiffType::Control, 124 Opacity O = Opacity::Transparent) { 125 switch (L) { 126 case Length::Empty: 127 return maybeOpaque("", O == Opacity::Opaque); 128 case Length::Small: 129 return maybeOpaque(getSmallString(D), O == Opacity::Opaque); 130 case Length::Large: 131 return maybeOpaque(getLargeString(D), O == Opacity::Opaque); 132 case Length::Huge: 133 return maybeOpaque(getHugeString(D), O == Opacity::Opaque); 134 } 135 } 136 137 template <class Length, class Opaque> 138 struct StringConstructDestroyCStr { 139 static void run(benchmark::State& state) { 140 for (auto _ : state) { 141 benchmark::DoNotOptimize( 142 makeString(Length(), DiffType::Control, Opaque())); 143 } 144 } 145 146 static std::string name() { 147 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name(); 148 } 149 }; 150 151 template <class Length, bool MeasureCopy, bool MeasureDestroy> 152 static void StringCopyAndDestroy(benchmark::State& state) { 153 static constexpr size_t NumStrings = 1024; 154 auto Orig = makeString(Length()); 155 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings]; 156 157 while (state.KeepRunningBatch(NumStrings)) { 158 if (!MeasureCopy) 159 state.PauseTiming(); 160 for (size_t I = 0; I < NumStrings; ++I) { 161 ::new (static_cast<void*>(Storage + I)) std::string(Orig); 162 } 163 if (!MeasureCopy) 164 state.ResumeTiming(); 165 if (!MeasureDestroy) 166 state.PauseTiming(); 167 for (size_t I = 0; I < NumStrings; ++I) { 168 using S = std::string; 169 reinterpret_cast<S*>(Storage + I)->~S(); 170 } 171 if (!MeasureDestroy) 172 state.ResumeTiming(); 173 } 174 } 175 176 template <class Length> 177 struct StringCopy { 178 static void run(benchmark::State& state) { 179 StringCopyAndDestroy<Length, true, false>(state); 180 } 181 182 static std::string name() { return "BM_StringCopy" + Length::name(); } 183 }; 184 185 template <class Length> 186 struct StringDestroy { 187 static void run(benchmark::State& state) { 188 StringCopyAndDestroy<Length, false, true>(state); 189 } 190 191 static std::string name() { return "BM_StringDestroy" + Length::name(); } 192 }; 193 194 template <class Length> 195 struct StringMove { 196 static void run(benchmark::State& state) { 197 // Keep two object locations and move construct back and forth. 198 std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2]; 199 using S = std::string; 200 size_t I = 0; 201 S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length())); 202 for (auto _ : state) { 203 // Switch locations. 204 I ^= 1; 205 benchmark::DoNotOptimize(Storage); 206 // Move construct into the new location, 207 S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS)); 208 // then destroy the old one. 209 newS->~S(); 210 newS = tmpS; 211 } 212 newS->~S(); 213 } 214 215 static std::string name() { return "BM_StringMove" + Length::name(); } 216 }; 217 218 enum class Relation { Eq, Less, Compare }; 219 struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> { 220 static constexpr const char* Names[] = {"Eq", "Less", "Compare"}; 221 }; 222 223 template <class Rel, class LHLength, class RHLength, class DiffType> 224 struct StringRelational { 225 static void run(benchmark::State& state) { 226 auto Lhs = makeString(RHLength()); 227 auto Rhs = makeString(LHLength(), DiffType()); 228 for (auto _ : state) { 229 benchmark::DoNotOptimize(Lhs); 230 benchmark::DoNotOptimize(Rhs); 231 switch (Rel()) { 232 case Relation::Eq: 233 benchmark::DoNotOptimize(Lhs == Rhs); 234 break; 235 case Relation::Less: 236 benchmark::DoNotOptimize(Lhs < Rhs); 237 break; 238 case Relation::Compare: 239 benchmark::DoNotOptimize(Lhs.compare(Rhs)); 240 break; 241 } 242 } 243 } 244 245 static bool skip() { 246 // Eq is commutative, so skip half the matrix. 247 if (Rel() == Relation::Eq && LHLength() > RHLength()) 248 return true; 249 // We only care about control when the lengths differ. 250 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control) 251 return true; 252 // For empty, only control matters. 253 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control) 254 return true; 255 return false; 256 } 257 258 static std::string name() { 259 return "BM_StringRelational" + Rel::name() + LHLength::name() + 260 RHLength::name() + DiffType::name(); 261 } 262 }; 263 264 enum class Depth { Shallow, Deep }; 265 struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> { 266 static constexpr const char* Names[] = {"Shallow", "Deep"}; 267 }; 268 269 enum class Temperature { Hot, Cold }; 270 struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> { 271 static constexpr const char* Names[] = {"Hot", "Cold"}; 272 }; 273 274 template <class Temperature, class Depth, class Length> 275 struct StringRead { 276 void run(benchmark::State& state) const { 277 static constexpr size_t NumStrings = 278 Temperature() == ::Temperature::Hot 279 ? 1 << 10 280 : /* Enough strings to overflow the cache */ 1 << 20; 281 static_assert((NumStrings & (NumStrings - 1)) == 0, 282 "NumStrings should be a power of two to reduce overhead."); 283 284 std::vector<std::string> Values(NumStrings, makeString(Length())); 285 size_t I = 0; 286 for (auto _ : state) { 287 // Jump long enough to defeat cache locality, and use a value that is 288 // coprime with NumStrings to ensure we visit every element. 289 I = (I + 17) % NumStrings; 290 const auto& V = Values[I]; 291 292 // Read everything first. Escaping data() through DoNotOptimize might 293 // cause the compiler to have to recalculate information about `V` due to 294 // aliasing. 295 const char* const Data = V.data(); 296 const size_t Size = V.size(); 297 benchmark::DoNotOptimize(Data); 298 benchmark::DoNotOptimize(Size); 299 if (Depth() == ::Depth::Deep) { 300 // Read into the payload. This mainly shows the benefit of SSO when the 301 // data is cold. 302 benchmark::DoNotOptimize(*Data); 303 } 304 } 305 } 306 307 static bool skip() { 308 // Huge does not give us anything that Large doesn't have. Skip it. 309 if (Length() == ::Length::Huge) { 310 return true; 311 } 312 return false; 313 } 314 315 std::string name() const { 316 return "BM_StringRead" + Temperature::name() + Depth::name() + 317 Length::name(); 318 } 319 }; 320 321 void sanityCheckGeneratedStrings() { 322 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 323 const auto LhsString = makeString(Lhs); 324 for (auto Rhs : 325 {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 326 if (Lhs > Rhs) 327 continue; 328 const auto RhsString = makeString(Rhs); 329 330 // The smaller one must be a prefix of the larger one. 331 if (RhsString.find(LhsString) != 0) { 332 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n", 333 static_cast<int>(Lhs), static_cast<int>(Rhs)); 334 std::abort(); 335 } 336 } 337 } 338 // Verify the autogenerated diffs 339 for (auto L : {Length::Small, Length::Large, Length::Huge}) { 340 const auto Control = makeString(L); 341 const auto Verify = [&](std::string Exp, size_t Pos) { 342 // Only change on the Pos char. 343 if (Control[Pos] != Exp[Pos]) { 344 Exp[Pos] = Control[Pos]; 345 if (Control == Exp) 346 return; 347 } 348 fprintf(stderr, "Invalid autogenerated diff with size %d\n", 349 static_cast<int>(L)); 350 std::abort(); 351 }; 352 Verify(makeString(L, DiffType::ChangeFirst), 0); 353 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2); 354 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1); 355 } 356 } 357 358 int main(int argc, char** argv) { 359 benchmark::Initialize(&argc, argv); 360 if (benchmark::ReportUnrecognizedArguments(argc, argv)) 361 return 1; 362 363 sanityCheckGeneratedStrings(); 364 365 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths, 366 AllOpacity>(); 367 makeCartesianProductBenchmark<StringCopy, AllLengths>(); 368 makeCartesianProductBenchmark<StringMove, AllLengths>(); 369 makeCartesianProductBenchmark<StringDestroy, AllLengths>(); 370 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths, 371 AllLengths, AllDiffTypes>(); 372 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths, 373 AllLengths>(); 374 benchmark::RunSpecifiedBenchmarks(); 375 } 376