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 static constexpr char kSmallStringLiteral[] = "012345678"; 77 78 TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) { 79 switch (D) { 80 case DiffType::Control: 81 return kSmallStringLiteral; 82 case DiffType::ChangeFirst: 83 return "-12345678"; 84 case DiffType::ChangeMiddle: 85 return "0123-5678"; 86 case DiffType::ChangeLast: 87 return "01234567-"; 88 } 89 } 90 91 TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) { 92 #define LARGE_STRING_FIRST "123456789012345678901234567890" 93 #define LARGE_STRING_SECOND "234567890123456789012345678901" 94 switch (D) { 95 case DiffType::Control: 96 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 97 case DiffType::ChangeFirst: 98 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 99 case DiffType::ChangeMiddle: 100 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2"; 101 case DiffType::ChangeLast: 102 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-"; 103 } 104 } 105 106 TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) { 107 #define HUGE_STRING0 "0123456789" 108 #define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 109 #define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 110 #define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 111 #define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 112 switch (D) { 113 case DiffType::Control: 114 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 115 case DiffType::ChangeFirst: 116 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 117 case DiffType::ChangeMiddle: 118 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789"; 119 case DiffType::ChangeLast: 120 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-"; 121 } 122 } 123 124 TEST_ALWAYS_INLINE std::string makeString(Length L, 125 DiffType D = DiffType::Control, 126 Opacity O = Opacity::Transparent) { 127 switch (L) { 128 case Length::Empty: 129 return maybeOpaque("", O == Opacity::Opaque); 130 case Length::Small: 131 return maybeOpaque(getSmallString(D), O == Opacity::Opaque); 132 case Length::Large: 133 return maybeOpaque(getLargeString(D), O == Opacity::Opaque); 134 case Length::Huge: 135 return maybeOpaque(getHugeString(D), O == Opacity::Opaque); 136 } 137 } 138 139 template <class Length, class Opaque> 140 struct StringConstructDestroyCStr { 141 static void run(benchmark::State& state) { 142 for (auto _ : state) { 143 benchmark::DoNotOptimize( 144 makeString(Length(), DiffType::Control, Opaque())); 145 } 146 } 147 148 static std::string name() { 149 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name(); 150 } 151 }; 152 153 template <class Length, bool MeasureCopy, bool MeasureDestroy> 154 static void StringCopyAndDestroy(benchmark::State& state) { 155 static constexpr size_t NumStrings = 1024; 156 auto Orig = makeString(Length()); 157 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings]; 158 159 while (state.KeepRunningBatch(NumStrings)) { 160 if (!MeasureCopy) 161 state.PauseTiming(); 162 for (size_t I = 0; I < NumStrings; ++I) { 163 ::new (static_cast<void*>(Storage + I)) std::string(Orig); 164 } 165 if (!MeasureCopy) 166 state.ResumeTiming(); 167 if (!MeasureDestroy) 168 state.PauseTiming(); 169 for (size_t I = 0; I < NumStrings; ++I) { 170 using S = std::string; 171 reinterpret_cast<S*>(Storage + I)->~S(); 172 } 173 if (!MeasureDestroy) 174 state.ResumeTiming(); 175 } 176 } 177 178 template <class Length> 179 struct StringCopy { 180 static void run(benchmark::State& state) { 181 StringCopyAndDestroy<Length, true, false>(state); 182 } 183 184 static std::string name() { return "BM_StringCopy" + Length::name(); } 185 }; 186 187 template <class Length> 188 struct StringDestroy { 189 static void run(benchmark::State& state) { 190 StringCopyAndDestroy<Length, false, true>(state); 191 } 192 193 static std::string name() { return "BM_StringDestroy" + Length::name(); } 194 }; 195 196 template <class Length> 197 struct StringMove { 198 static void run(benchmark::State& state) { 199 // Keep two object locations and move construct back and forth. 200 std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2]; 201 using S = std::string; 202 size_t I = 0; 203 S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length())); 204 for (auto _ : state) { 205 // Switch locations. 206 I ^= 1; 207 benchmark::DoNotOptimize(Storage); 208 // Move construct into the new location, 209 S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS)); 210 // then destroy the old one. 211 newS->~S(); 212 newS = tmpS; 213 } 214 newS->~S(); 215 } 216 217 static std::string name() { return "BM_StringMove" + Length::name(); } 218 }; 219 220 enum class Relation { Eq, Less, Compare }; 221 struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> { 222 static constexpr const char* Names[] = {"Eq", "Less", "Compare"}; 223 }; 224 225 template <class Rel, class LHLength, class RHLength, class DiffType> 226 struct StringRelational { 227 static void run(benchmark::State& state) { 228 auto Lhs = makeString(RHLength()); 229 auto Rhs = makeString(LHLength(), DiffType()); 230 for (auto _ : state) { 231 benchmark::DoNotOptimize(Lhs); 232 benchmark::DoNotOptimize(Rhs); 233 switch (Rel()) { 234 case Relation::Eq: 235 benchmark::DoNotOptimize(Lhs == Rhs); 236 break; 237 case Relation::Less: 238 benchmark::DoNotOptimize(Lhs < Rhs); 239 break; 240 case Relation::Compare: 241 benchmark::DoNotOptimize(Lhs.compare(Rhs)); 242 break; 243 } 244 } 245 } 246 247 static bool skip() { 248 // Eq is commutative, so skip half the matrix. 249 if (Rel() == Relation::Eq && LHLength() > RHLength()) 250 return true; 251 // We only care about control when the lengths differ. 252 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control) 253 return true; 254 // For empty, only control matters. 255 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control) 256 return true; 257 return false; 258 } 259 260 static std::string name() { 261 return "BM_StringRelational" + Rel::name() + LHLength::name() + 262 RHLength::name() + DiffType::name(); 263 } 264 }; 265 266 template <class Rel, class LHLength, class DiffType> 267 struct StringRelationalLiteral { 268 static void run(benchmark::State& state) { 269 auto Lhs = makeString(LHLength(), DiffType()); 270 for (auto _ : state) { 271 benchmark::DoNotOptimize(Lhs); 272 switch (Rel()) { 273 case Relation::Eq: 274 benchmark::DoNotOptimize(Lhs == kSmallStringLiteral); 275 break; 276 case Relation::Less: 277 benchmark::DoNotOptimize(Lhs < kSmallStringLiteral); 278 break; 279 case Relation::Compare: 280 benchmark::DoNotOptimize(Lhs.compare(kSmallStringLiteral)); 281 break; 282 } 283 } 284 } 285 286 static bool skip() { 287 // Doesn't matter how they differ if they have different size. 288 if (LHLength() != Length::Small && DiffType() != ::DiffType::Control) 289 return true; 290 // We don't need huge. Doensn't give anything different than Large. 291 if (LHLength() == Length::Huge) 292 return true; 293 return false; 294 } 295 296 static std::string name() { 297 return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() + 298 DiffType::name(); 299 } 300 }; 301 302 enum class Depth { Shallow, Deep }; 303 struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> { 304 static constexpr const char* Names[] = {"Shallow", "Deep"}; 305 }; 306 307 enum class Temperature { Hot, Cold }; 308 struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> { 309 static constexpr const char* Names[] = {"Hot", "Cold"}; 310 }; 311 312 template <class Temperature, class Depth, class Length> 313 struct StringRead { 314 void run(benchmark::State& state) const { 315 static constexpr size_t NumStrings = 316 Temperature() == ::Temperature::Hot 317 ? 1 << 10 318 : /* Enough strings to overflow the cache */ 1 << 20; 319 static_assert((NumStrings & (NumStrings - 1)) == 0, 320 "NumStrings should be a power of two to reduce overhead."); 321 322 std::vector<std::string> Values(NumStrings, makeString(Length())); 323 size_t I = 0; 324 for (auto _ : state) { 325 // Jump long enough to defeat cache locality, and use a value that is 326 // coprime with NumStrings to ensure we visit every element. 327 I = (I + 17) % NumStrings; 328 const auto& V = Values[I]; 329 330 // Read everything first. Escaping data() through DoNotOptimize might 331 // cause the compiler to have to recalculate information about `V` due to 332 // aliasing. 333 const char* const Data = V.data(); 334 const size_t Size = V.size(); 335 benchmark::DoNotOptimize(Data); 336 benchmark::DoNotOptimize(Size); 337 if (Depth() == ::Depth::Deep) { 338 // Read into the payload. This mainly shows the benefit of SSO when the 339 // data is cold. 340 benchmark::DoNotOptimize(*Data); 341 } 342 } 343 } 344 345 static bool skip() { 346 // Huge does not give us anything that Large doesn't have. Skip it. 347 if (Length() == ::Length::Huge) { 348 return true; 349 } 350 return false; 351 } 352 353 std::string name() const { 354 return "BM_StringRead" + Temperature::name() + Depth::name() + 355 Length::name(); 356 } 357 }; 358 359 void sanityCheckGeneratedStrings() { 360 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 361 const auto LhsString = makeString(Lhs); 362 for (auto Rhs : 363 {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 364 if (Lhs > Rhs) 365 continue; 366 const auto RhsString = makeString(Rhs); 367 368 // The smaller one must be a prefix of the larger one. 369 if (RhsString.find(LhsString) != 0) { 370 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n", 371 static_cast<int>(Lhs), static_cast<int>(Rhs)); 372 std::abort(); 373 } 374 } 375 } 376 // Verify the autogenerated diffs 377 for (auto L : {Length::Small, Length::Large, Length::Huge}) { 378 const auto Control = makeString(L); 379 const auto Verify = [&](std::string Exp, size_t Pos) { 380 // Only change on the Pos char. 381 if (Control[Pos] != Exp[Pos]) { 382 Exp[Pos] = Control[Pos]; 383 if (Control == Exp) 384 return; 385 } 386 fprintf(stderr, "Invalid autogenerated diff with size %d\n", 387 static_cast<int>(L)); 388 std::abort(); 389 }; 390 Verify(makeString(L, DiffType::ChangeFirst), 0); 391 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2); 392 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1); 393 } 394 } 395 396 int main(int argc, char** argv) { 397 benchmark::Initialize(&argc, argv); 398 if (benchmark::ReportUnrecognizedArguments(argc, argv)) 399 return 1; 400 401 sanityCheckGeneratedStrings(); 402 403 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths, 404 AllOpacity>(); 405 makeCartesianProductBenchmark<StringCopy, AllLengths>(); 406 makeCartesianProductBenchmark<StringMove, AllLengths>(); 407 makeCartesianProductBenchmark<StringDestroy, AllLengths>(); 408 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths, 409 AllLengths, AllDiffTypes>(); 410 makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations, 411 AllLengths, AllDiffTypes>(); 412 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths, 413 AllLengths>(); 414 benchmark::RunSpecifiedBenchmarks(); 415 } 416