1 2 #include <cstdint> 3 #include <new> 4 #include <vector> 5 6 #include "CartesianBenchmarks.h" 7 #include "GenerateInput.h" 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 SmallStringLiteral[] = "012345678"; 77 78 TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) { 79 switch (D) { 80 case DiffType::Control: 81 return SmallStringLiteral; 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 static constexpr char LargeStringLiteral[] = 92 "012345678901234567890123456789012345678901234567890123456789012"; 93 94 TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) { 95 #define LARGE_STRING_FIRST "123456789012345678901234567890" 96 #define LARGE_STRING_SECOND "234567890123456789012345678901" 97 switch (D) { 98 case DiffType::Control: 99 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 100 case DiffType::ChangeFirst: 101 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2"; 102 case DiffType::ChangeMiddle: 103 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2"; 104 case DiffType::ChangeLast: 105 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-"; 106 } 107 } 108 109 TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) { 110 #define HUGE_STRING0 "0123456789" 111 #define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 112 #define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 113 #define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 114 #define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 115 switch (D) { 116 case DiffType::Control: 117 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 118 case DiffType::ChangeFirst: 119 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789"; 120 case DiffType::ChangeMiddle: 121 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789"; 122 case DiffType::ChangeLast: 123 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-"; 124 } 125 } 126 127 TEST_ALWAYS_INLINE const char* getString(Length L, 128 DiffType D = DiffType::Control) { 129 switch (L) { 130 case Length::Empty: 131 return ""; 132 case Length::Small: 133 return getSmallString(D); 134 case Length::Large: 135 return getLargeString(D); 136 case Length::Huge: 137 return getHugeString(D); 138 } 139 } 140 141 TEST_ALWAYS_INLINE std::string makeString(Length L, 142 DiffType D = DiffType::Control, 143 Opacity O = Opacity::Transparent) { 144 switch (L) { 145 case Length::Empty: 146 return maybeOpaque("", O == Opacity::Opaque); 147 case Length::Small: 148 return maybeOpaque(getSmallString(D), O == Opacity::Opaque); 149 case Length::Large: 150 return maybeOpaque(getLargeString(D), O == Opacity::Opaque); 151 case Length::Huge: 152 return maybeOpaque(getHugeString(D), O == Opacity::Opaque); 153 } 154 } 155 156 template <class Length, class Opaque> 157 struct StringConstructDestroyCStr { 158 static void run(benchmark::State& state) { 159 for (auto _ : state) { 160 benchmark::DoNotOptimize( 161 makeString(Length(), DiffType::Control, Opaque())); 162 } 163 } 164 165 static std::string name() { 166 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name(); 167 } 168 }; 169 170 template <class Length, bool MeasureCopy, bool MeasureDestroy> 171 static void StringCopyAndDestroy(benchmark::State& state) { 172 static constexpr size_t NumStrings = 1024; 173 auto Orig = makeString(Length()); 174 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings]; 175 176 while (state.KeepRunningBatch(NumStrings)) { 177 if (!MeasureCopy) 178 state.PauseTiming(); 179 for (size_t I = 0; I < NumStrings; ++I) { 180 ::new (static_cast<void*>(Storage + I)) std::string(Orig); 181 } 182 if (!MeasureCopy) 183 state.ResumeTiming(); 184 if (!MeasureDestroy) 185 state.PauseTiming(); 186 for (size_t I = 0; I < NumStrings; ++I) { 187 using S = std::string; 188 reinterpret_cast<S*>(Storage + I)->~S(); 189 } 190 if (!MeasureDestroy) 191 state.ResumeTiming(); 192 } 193 } 194 195 template <class Length> 196 struct StringCopy { 197 static void run(benchmark::State& state) { 198 StringCopyAndDestroy<Length, true, false>(state); 199 } 200 201 static std::string name() { return "BM_StringCopy" + Length::name(); } 202 }; 203 204 template <class Length> 205 struct StringDestroy { 206 static void run(benchmark::State& state) { 207 StringCopyAndDestroy<Length, false, true>(state); 208 } 209 210 static std::string name() { return "BM_StringDestroy" + Length::name(); } 211 }; 212 213 template <class Length> 214 struct StringMove { 215 static void run(benchmark::State& state) { 216 // Keep two object locations and move construct back and forth. 217 std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2]; 218 using S = std::string; 219 size_t I = 0; 220 S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length())); 221 for (auto _ : state) { 222 // Switch locations. 223 I ^= 1; 224 benchmark::DoNotOptimize(Storage); 225 // Move construct into the new location, 226 S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS)); 227 // then destroy the old one. 228 newS->~S(); 229 newS = tmpS; 230 } 231 newS->~S(); 232 } 233 234 static std::string name() { return "BM_StringMove" + Length::name(); } 235 }; 236 237 template <class Length, class Opaque> 238 struct StringResizeDefaultInit { 239 static void run(benchmark::State& state) { 240 constexpr bool opaque = Opaque{} == Opacity::Opaque; 241 constexpr int kNumStrings = 4 << 10; 242 size_t length = makeString(Length()).size(); 243 std::string strings[kNumStrings]; 244 while (state.KeepRunningBatch(kNumStrings)) { 245 state.PauseTiming(); 246 for (int i = 0; i < kNumStrings; ++i) { 247 std::string().swap(strings[i]); 248 } 249 benchmark::DoNotOptimize(strings); 250 state.ResumeTiming(); 251 for (int i = 0; i < kNumStrings; ++i) { 252 strings[i].__resize_default_init(maybeOpaque(length, opaque)); 253 } 254 } 255 } 256 257 static std::string name() { 258 return "BM_StringResizeDefaultInit" + Length::name() + Opaque::name(); 259 } 260 }; 261 262 template <class Length, class Opaque> 263 struct StringAssignStr { 264 static void run(benchmark::State& state) { 265 constexpr bool opaque = Opaque{} == Opacity::Opaque; 266 constexpr int kNumStrings = 4 << 10; 267 std::string src = makeString(Length()); 268 std::string strings[kNumStrings]; 269 while (state.KeepRunningBatch(kNumStrings)) { 270 state.PauseTiming(); 271 for (int i = 0; i < kNumStrings; ++i) { 272 std::string().swap(strings[i]); 273 } 274 benchmark::DoNotOptimize(strings); 275 state.ResumeTiming(); 276 for (int i = 0; i < kNumStrings; ++i) { 277 strings[i] = *maybeOpaque(&src, opaque); 278 } 279 } 280 } 281 282 static std::string name() { 283 return "BM_StringAssignStr" + Length::name() + Opaque::name(); 284 } 285 }; 286 287 template <class Length, class Opaque> 288 struct StringAssignAsciiz { 289 static void run(benchmark::State& state) { 290 constexpr bool opaque = Opaque{} == Opacity::Opaque; 291 constexpr int kNumStrings = 4 << 10; 292 std::string strings[kNumStrings]; 293 while (state.KeepRunningBatch(kNumStrings)) { 294 state.PauseTiming(); 295 for (int i = 0; i < kNumStrings; ++i) { 296 std::string().swap(strings[i]); 297 } 298 benchmark::DoNotOptimize(strings); 299 state.ResumeTiming(); 300 for (int i = 0; i < kNumStrings; ++i) { 301 strings[i] = maybeOpaque(getString(Length()), opaque); 302 } 303 } 304 } 305 306 static std::string name() { 307 return "BM_StringAssignAsciiz" + Length::name() + Opaque::name(); 308 } 309 }; 310 311 template <class Length, class Opaque> 312 struct StringEraseToEnd { 313 static void run(benchmark::State& state) { 314 constexpr bool opaque = Opaque{} == Opacity::Opaque; 315 constexpr int kNumStrings = 4 << 10; 316 std::string strings[kNumStrings]; 317 const int mid = makeString(Length()).size() / 2; 318 while (state.KeepRunningBatch(kNumStrings)) { 319 state.PauseTiming(); 320 for (int i = 0; i < kNumStrings; ++i) { 321 strings[i] = makeString(Length()); 322 } 323 benchmark::DoNotOptimize(strings); 324 state.ResumeTiming(); 325 for (int i = 0; i < kNumStrings; ++i) { 326 strings[i].erase(maybeOpaque(mid, opaque), 327 maybeOpaque(std::string::npos, opaque)); 328 } 329 } 330 } 331 332 static std::string name() { 333 return "BM_StringEraseToEnd" + Length::name() + Opaque::name(); 334 } 335 }; 336 337 template <class Length, class Opaque> 338 struct StringEraseWithMove { 339 static void run(benchmark::State& state) { 340 constexpr bool opaque = Opaque{} == Opacity::Opaque; 341 constexpr int kNumStrings = 4 << 10; 342 std::string strings[kNumStrings]; 343 const int n = makeString(Length()).size() / 2; 344 const int pos = n / 2; 345 while (state.KeepRunningBatch(kNumStrings)) { 346 state.PauseTiming(); 347 for (int i = 0; i < kNumStrings; ++i) { 348 strings[i] = makeString(Length()); 349 } 350 benchmark::DoNotOptimize(strings); 351 state.ResumeTiming(); 352 for (int i = 0; i < kNumStrings; ++i) { 353 strings[i].erase(maybeOpaque(pos, opaque), maybeOpaque(n, opaque)); 354 } 355 } 356 } 357 358 static std::string name() { 359 return "BM_StringEraseWithMove" + Length::name() + Opaque::name(); 360 } 361 }; 362 363 template <class Opaque> 364 struct StringAssignAsciizMix { 365 static void run(benchmark::State& state) { 366 constexpr auto O = Opaque{}; 367 constexpr auto D = DiffType::Control; 368 constexpr int kNumStrings = 4 << 10; 369 std::string strings[kNumStrings]; 370 while (state.KeepRunningBatch(kNumStrings)) { 371 state.PauseTiming(); 372 for (int i = 0; i < kNumStrings; ++i) { 373 std::string().swap(strings[i]); 374 } 375 benchmark::DoNotOptimize(strings); 376 state.ResumeTiming(); 377 for (int i = 0; i < kNumStrings - 7; i += 8) { 378 strings[i + 0] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 379 strings[i + 1] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 380 strings[i + 2] = maybeOpaque(getLargeString(D), O == Opacity::Opaque); 381 strings[i + 3] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 382 strings[i + 4] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 383 strings[i + 5] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 384 strings[i + 6] = maybeOpaque(getLargeString(D), O == Opacity::Opaque); 385 strings[i + 7] = maybeOpaque(getSmallString(D), O == Opacity::Opaque); 386 } 387 } 388 } 389 390 static std::string name() { 391 return "BM_StringAssignAsciizMix" + Opaque::name(); 392 } 393 }; 394 395 enum class Relation { Eq, Less, Compare }; 396 struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> { 397 static constexpr const char* Names[] = {"Eq", "Less", "Compare"}; 398 }; 399 400 template <class Rel, class LHLength, class RHLength, class DiffType> 401 struct StringRelational { 402 static void run(benchmark::State& state) { 403 auto Lhs = makeString(RHLength()); 404 auto Rhs = makeString(LHLength(), DiffType()); 405 for (auto _ : state) { 406 benchmark::DoNotOptimize(Lhs); 407 benchmark::DoNotOptimize(Rhs); 408 switch (Rel()) { 409 case Relation::Eq: 410 benchmark::DoNotOptimize(Lhs == Rhs); 411 break; 412 case Relation::Less: 413 benchmark::DoNotOptimize(Lhs < Rhs); 414 break; 415 case Relation::Compare: 416 benchmark::DoNotOptimize(Lhs.compare(Rhs)); 417 break; 418 } 419 } 420 } 421 422 static bool skip() { 423 // Eq is commutative, so skip half the matrix. 424 if (Rel() == Relation::Eq && LHLength() > RHLength()) 425 return true; 426 // We only care about control when the lengths differ. 427 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control) 428 return true; 429 // For empty, only control matters. 430 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control) 431 return true; 432 return false; 433 } 434 435 static std::string name() { 436 return "BM_StringRelational" + Rel::name() + LHLength::name() + 437 RHLength::name() + DiffType::name(); 438 } 439 }; 440 441 template <class Rel, class LHLength, class RHLength, class DiffType> 442 struct StringRelationalLiteral { 443 static void run(benchmark::State& state) { 444 auto Lhs = makeString(LHLength(), DiffType()); 445 for (auto _ : state) { 446 benchmark::DoNotOptimize(Lhs); 447 constexpr const char* Literal = RHLength::value == Length::Empty 448 ? "" 449 : RHLength::value == Length::Small 450 ? SmallStringLiteral 451 : LargeStringLiteral; 452 switch (Rel()) { 453 case Relation::Eq: 454 benchmark::DoNotOptimize(Lhs == Literal); 455 break; 456 case Relation::Less: 457 benchmark::DoNotOptimize(Lhs < Literal); 458 break; 459 case Relation::Compare: 460 benchmark::DoNotOptimize(Lhs.compare(Literal)); 461 break; 462 } 463 } 464 } 465 466 static bool skip() { 467 // Doesn't matter how they differ if they have different size. 468 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control) 469 return true; 470 // We don't need huge. Doensn't give anything different than Large. 471 if (LHLength() == Length::Huge || RHLength() == Length::Huge) 472 return true; 473 return false; 474 } 475 476 static std::string name() { 477 return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() + 478 RHLength::name() + DiffType::name(); 479 } 480 }; 481 482 enum class Depth { Shallow, Deep }; 483 struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> { 484 static constexpr const char* Names[] = {"Shallow", "Deep"}; 485 }; 486 487 enum class Temperature { Hot, Cold }; 488 struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> { 489 static constexpr const char* Names[] = {"Hot", "Cold"}; 490 }; 491 492 template <class Temperature, class Depth, class Length> 493 struct StringRead { 494 void run(benchmark::State& state) const { 495 static constexpr size_t NumStrings = 496 Temperature() == ::Temperature::Hot 497 ? 1 << 10 498 : /* Enough strings to overflow the cache */ 1 << 20; 499 static_assert((NumStrings & (NumStrings - 1)) == 0, 500 "NumStrings should be a power of two to reduce overhead."); 501 502 std::vector<std::string> Values(NumStrings, makeString(Length())); 503 size_t I = 0; 504 for (auto _ : state) { 505 // Jump long enough to defeat cache locality, and use a value that is 506 // coprime with NumStrings to ensure we visit every element. 507 I = (I + 17) % NumStrings; 508 const auto& V = Values[I]; 509 510 // Read everything first. Escaping data() through DoNotOptimize might 511 // cause the compiler to have to recalculate information about `V` due to 512 // aliasing. 513 const char* const Data = V.data(); 514 const size_t Size = V.size(); 515 benchmark::DoNotOptimize(Data); 516 benchmark::DoNotOptimize(Size); 517 if (Depth() == ::Depth::Deep) { 518 // Read into the payload. This mainly shows the benefit of SSO when the 519 // data is cold. 520 benchmark::DoNotOptimize(*Data); 521 } 522 } 523 } 524 525 static bool skip() { 526 // Huge does not give us anything that Large doesn't have. Skip it. 527 if (Length() == ::Length::Huge) { 528 return true; 529 } 530 return false; 531 } 532 533 std::string name() const { 534 return "BM_StringRead" + Temperature::name() + Depth::name() + 535 Length::name(); 536 } 537 }; 538 539 void sanityCheckGeneratedStrings() { 540 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 541 const auto LhsString = makeString(Lhs); 542 for (auto Rhs : 543 {Length::Empty, Length::Small, Length::Large, Length::Huge}) { 544 if (Lhs > Rhs) 545 continue; 546 const auto RhsString = makeString(Rhs); 547 548 // The smaller one must be a prefix of the larger one. 549 if (RhsString.find(LhsString) != 0) { 550 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n", 551 static_cast<int>(Lhs), static_cast<int>(Rhs)); 552 std::abort(); 553 } 554 } 555 } 556 // Verify the autogenerated diffs 557 for (auto L : {Length::Small, Length::Large, Length::Huge}) { 558 const auto Control = makeString(L); 559 const auto Verify = [&](std::string Exp, size_t Pos) { 560 // Only change on the Pos char. 561 if (Control[Pos] != Exp[Pos]) { 562 Exp[Pos] = Control[Pos]; 563 if (Control == Exp) 564 return; 565 } 566 fprintf(stderr, "Invalid autogenerated diff with size %d\n", 567 static_cast<int>(L)); 568 std::abort(); 569 }; 570 Verify(makeString(L, DiffType::ChangeFirst), 0); 571 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2); 572 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1); 573 } 574 } 575 576 // Some small codegen thunks to easily see generated code. 577 bool StringEqString(const std::string& a, const std::string& b) { 578 return a == b; 579 } 580 bool StringEqCStr(const std::string& a, const char* b) { return a == b; } 581 bool CStrEqString(const char* a, const std::string& b) { return a == b; } 582 bool StringEqCStrLiteralEmpty(const std::string& a) { 583 return a == ""; 584 } 585 bool StringEqCStrLiteralSmall(const std::string& a) { 586 return a == SmallStringLiteral; 587 } 588 bool StringEqCStrLiteralLarge(const std::string& a) { 589 return a == LargeStringLiteral; 590 } 591 592 int main(int argc, char** argv) { 593 benchmark::Initialize(&argc, argv); 594 if (benchmark::ReportUnrecognizedArguments(argc, argv)) 595 return 1; 596 597 sanityCheckGeneratedStrings(); 598 599 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths, 600 AllOpacity>(); 601 602 makeCartesianProductBenchmark<StringAssignStr, AllLengths, AllOpacity>(); 603 makeCartesianProductBenchmark<StringAssignAsciiz, AllLengths, AllOpacity>(); 604 makeCartesianProductBenchmark<StringAssignAsciizMix, AllOpacity>(); 605 606 makeCartesianProductBenchmark<StringCopy, AllLengths>(); 607 makeCartesianProductBenchmark<StringMove, AllLengths>(); 608 makeCartesianProductBenchmark<StringDestroy, AllLengths>(); 609 makeCartesianProductBenchmark<StringResizeDefaultInit, AllLengths, 610 AllOpacity>(); 611 makeCartesianProductBenchmark<StringEraseToEnd, AllLengths, AllOpacity>(); 612 makeCartesianProductBenchmark<StringEraseWithMove, AllLengths, AllOpacity>(); 613 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths, 614 AllLengths, AllDiffTypes>(); 615 makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations, 616 AllLengths, AllLengths, AllDiffTypes>(); 617 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths, 618 AllLengths>(); 619 benchmark::RunSpecifiedBenchmarks(); 620 621 if (argc < 0) { 622 // ODR-use the functions to force them being generated in the binary. 623 auto functions = std::make_tuple( 624 StringEqString, StringEqCStr, CStrEqString, StringEqCStrLiteralEmpty, 625 StringEqCStrLiteralSmall, StringEqCStrLiteralLarge); 626 printf("%p", &functions); 627 } 628 } 629