hash.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #include <library/cpp/case_insensitive_string/case_insensitive_string.h>
  2. #include <library/cpp/case_insensitive_string/ut_gtest/util/locale_guard.h>
  3. #include <benchmark/benchmark.h>
  4. #include <library/cpp/digest/murmur/murmur.h>
  5. #include <util/generic/hash_table.h>
  6. #include <util/generic/vector.h>
  7. #include <util/string/ascii.h>
  8. #include <util/random/random.h>
  9. namespace {
  10. // THash<TCaseInsensitiveStringBuf>::operator() is not inlined
  11. Y_NO_INLINE size_t NaiveHash(TCaseInsensitiveStringBuf str) noexcept {
  12. TMurmurHash2A<size_t> hash;
  13. for (size_t i = 0; i < str.size(); ++i) {
  14. char lower = std::tolower(str[i]);
  15. hash.Update(&lower, 1);
  16. }
  17. return hash.Value();
  18. }
  19. [[maybe_unused]] Y_NO_INLINE size_t OptimizedHashV1(TCaseInsensitiveStringBuf str) noexcept {
  20. TMurmurHash2A<size_t> hash;
  21. std::array<char, sizeof(size_t)> buf;
  22. size_t headSize = str.size() - str.size() % buf.size();
  23. for (size_t i = 0; i < headSize; i += buf.size()) {
  24. for (size_t j = 0; j < buf.size(); ++j) {
  25. buf[j] = std::tolower(str[i + j]);
  26. }
  27. hash.Update(buf.data(), buf.size());
  28. }
  29. for (size_t i = headSize; i < str.size(); ++i) {
  30. char lower = std::tolower(str[i]);
  31. hash.Update(&lower, 1);
  32. }
  33. return hash.Value();
  34. }
  35. [[maybe_unused]] Y_NO_INLINE size_t OptimizedHashDuplicateTailLoop(TCaseInsensitiveStringBuf str) noexcept {
  36. TMurmurHash2A<size_t> hash;
  37. if (str.size() < sizeof(size_t)) {
  38. for (size_t i = 0; i < str.size(); ++i) {
  39. char lower = std::tolower(str[i]);
  40. hash.Update(&lower, 1);
  41. }
  42. return hash.Value();
  43. }
  44. std::array<char, sizeof(size_t)> buf;
  45. size_t headSize = str.size() - str.size() % buf.size();
  46. for (size_t i = 0; i < headSize; i += buf.size()) {
  47. for (size_t j = 0; j < buf.size(); ++j) {
  48. buf[j] = std::tolower(str[i + j]);
  49. }
  50. hash.Update(buf.data(), buf.size());
  51. }
  52. for (size_t i = headSize; i < str.size(); ++i) {
  53. char lower = std::tolower(str[i]);
  54. hash.Update(&lower, 1);
  55. }
  56. return hash.Value();
  57. }
  58. size_t HashTail(TMurmurHash2A<size_t>& hash, const char* data, size_t size) {
  59. for (size_t i = 0; i < size; ++i) {
  60. char lower = std::tolower(data[i]);
  61. hash.Update(&lower, 1);
  62. }
  63. return hash.Value();
  64. }
  65. [[maybe_unused]] Y_NO_INLINE size_t OptimizedHashDuplicateTailLoopInFunc(TCaseInsensitiveStringBuf str) noexcept {
  66. TMurmurHash2A<size_t> hash;
  67. if (str.size() < sizeof(size_t)) {
  68. return HashTail(hash, str.data(), str.size());
  69. }
  70. std::array<char, sizeof(size_t)> buf;
  71. size_t headSize = str.size() - str.size() % buf.size();
  72. for (size_t i = 0; i < headSize; i += buf.size()) {
  73. for (size_t j = 0; j < buf.size(); ++j) {
  74. buf[j] = std::tolower(str[i + j]);
  75. }
  76. hash.Update(buf.data(), buf.size());
  77. }
  78. return HashTail(hash, str.data() + headSize, str.size() - headSize);
  79. }
  80. // Currently shows the best performance, comparable with NaiveHash for short strings, only slower for empty strings.
  81. // Should be equivalent to OptimizedHashV1 after inlining HashTail, but for some reason it's a bit larger but at the same time faster than OptimizedHashV1.
  82. [[maybe_unused]] Y_NO_INLINE size_t OptimizedHashTailLoopInFunc(TCaseInsensitiveStringBuf str) noexcept {
  83. TMurmurHash2A<size_t> hash;
  84. std::array<char, sizeof(size_t)> buf;
  85. size_t headSize = str.size() - str.size() % buf.size();
  86. for (size_t i = 0; i < headSize; i += buf.size()) {
  87. for (size_t j = 0; j < buf.size(); ++j) {
  88. buf[j] = std::tolower(str[i + j]);
  89. }
  90. hash.Update(buf.data(), buf.size());
  91. }
  92. return HashTail(hash, str.data() + headSize, str.size() - headSize);
  93. }
  94. Y_FORCE_INLINE size_t DefaultHash(TCaseInsensitiveStringBuf str) {
  95. return ComputeHash(str);
  96. }
  97. Y_FORCE_INLINE size_t DefaultHashAscii(TCaseInsensitiveAsciiStringBuf str) {
  98. return ComputeHash(str);
  99. }
  100. }
  101. template <auto Impl, typename TTraits = TCaseInsensitiveCharTraits>
  102. void CaseInsensitiveHash(benchmark::State& state) {
  103. TLocaleGuard loc("C");
  104. Y_ABORT_IF(loc.Error());
  105. SetRandomSeed(123 + state.range());
  106. TBasicString<char, TTraits> str;
  107. for (int i = 0; i < state.range(); ++i) {
  108. str.push_back(RandomNumber<unsigned char>());
  109. }
  110. Y_ENSURE(Impl(str) == NaiveHash(str), "Hashes differ: got " << Impl(str) << ", expected " << NaiveHash(str));
  111. for (auto _ : state) {
  112. size_t hash = Impl(str);
  113. benchmark::DoNotOptimize(hash);
  114. }
  115. }
  116. template <auto Impl, typename TTraits = TCaseInsensitiveCharTraits>
  117. void CaseInsensitiveHashRandomSizes(benchmark::State& state) {
  118. TLocaleGuard loc("C");
  119. Y_ABORT_IF(loc.Error());
  120. SetRandomSeed(123);
  121. size_t minStrLen = static_cast<size_t>(state.range(0));
  122. size_t maxStrLen = static_cast<size_t>(state.range(1));
  123. static constexpr size_t nStrings = 64;
  124. TVector<TString> stringStorage(Reserve(nStrings));
  125. std::array<TBasicStringBuf<char, TTraits>, nStrings> strings;
  126. for (size_t i = 0; i < nStrings; ++i) {
  127. auto& str = stringStorage.emplace_back();
  128. size_t strLen = minStrLen + RandomNumber(maxStrLen - minStrLen + 1);
  129. for (size_t i = 0; i < strLen; ++i) {
  130. str.push_back(RandomNumber<unsigned char>());
  131. }
  132. strings[i] = str;
  133. }
  134. for (auto _ : state) {
  135. for (auto str : strings) {
  136. size_t hash = Impl(str);
  137. benchmark::DoNotOptimize(hash);
  138. }
  139. }
  140. }
  141. #define BENCH_ARGS ArgName("strlen")->DenseRange(0, sizeof(size_t) + 1)->RangeMultiplier(2)->Range(sizeof(size_t) * 2, 64)
  142. BENCHMARK(CaseInsensitiveHash<NaiveHash>)->BENCH_ARGS;
  143. BENCHMARK(CaseInsensitiveHash<DefaultHash>)->BENCH_ARGS;
  144. #ifdef BENCHMARK_ALL_IMPLS
  145. BENCHMARK(CaseInsensitiveHash<OptimizedHashV1>)->BENCH_ARGS;
  146. BENCHMARK(CaseInsensitiveHash<OptimizedHashDuplicateTailLoop>)->BENCH_ARGS;
  147. BENCHMARK(CaseInsensitiveHash<OptimizedHashDuplicateTailLoopInFunc>)->BENCH_ARGS;
  148. BENCHMARK(CaseInsensitiveHash<OptimizedHashTailLoopInFunc>)->BENCH_ARGS;
  149. #endif
  150. BENCHMARK(CaseInsensitiveHash<DefaultHashAscii, TCaseInsensitiveAsciiCharTraits>)->BENCH_ARGS;
  151. #undef BENCH_ARGS
  152. #define BENCH_ARGS \
  153. ArgNames({"minStrLen", "maxStrLen"}) \
  154. ->ArgPair(4, 16) /*Approximate http header lengths (real distribution may differ)*/ \
  155. ->ArgPair(4, 11) /*1/2 short, 1/2 long to check possible branch mispredictions*/ \
  156. ->ArgPair(1, 8) /*Mostly short strings to check performance regression*/
  157. BENCHMARK(CaseInsensitiveHashRandomSizes<NaiveHash>)->BENCH_ARGS;
  158. BENCHMARK(CaseInsensitiveHashRandomSizes<DefaultHash>)->BENCH_ARGS;
  159. BENCHMARK(CaseInsensitiveHashRandomSizes<DefaultHashAscii, TCaseInsensitiveAsciiCharTraits>)->BENCH_ARGS;
  160. #undef BENCH_ARGS