hash_benchmark.cc 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // Copyright 2018 The Abseil Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // https://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include <string>
  15. #include <type_traits>
  16. #include <typeindex>
  17. #include <utility>
  18. #include <vector>
  19. #include "absl/base/attributes.h"
  20. #include "absl/hash/hash.h"
  21. #include "absl/random/random.h"
  22. #include "absl/strings/cord.h"
  23. #include "absl/strings/cord_test_helpers.h"
  24. #include "absl/strings/string_view.h"
  25. #include "benchmark/benchmark.h"
  26. namespace {
  27. using absl::Hash;
  28. template <template <typename> class H, typename T>
  29. void RunBenchmark(benchmark::State& state, T value) {
  30. H<T> h;
  31. for (auto _ : state) {
  32. benchmark::DoNotOptimize(value);
  33. benchmark::DoNotOptimize(h(value));
  34. }
  35. }
  36. } // namespace
  37. template <typename T>
  38. using AbslHash = absl::Hash<T>;
  39. class TypeErasedInterface {
  40. public:
  41. virtual ~TypeErasedInterface() = default;
  42. template <typename H>
  43. friend H AbslHashValue(H state, const TypeErasedInterface& wrapper) {
  44. state = H::combine(std::move(state), std::type_index(typeid(wrapper)));
  45. wrapper.HashValue(absl::HashState::Create(&state));
  46. return state;
  47. }
  48. private:
  49. virtual void HashValue(absl::HashState state) const = 0;
  50. };
  51. template <typename T>
  52. struct TypeErasedAbslHash {
  53. class Wrapper : public TypeErasedInterface {
  54. public:
  55. explicit Wrapper(const T& value) : value_(value) {}
  56. private:
  57. void HashValue(absl::HashState state) const override {
  58. absl::HashState::combine(std::move(state), value_);
  59. }
  60. const T& value_;
  61. };
  62. size_t operator()(const T& value) {
  63. return absl::Hash<Wrapper>{}(Wrapper(value));
  64. }
  65. };
  66. template <typename FuncType>
  67. inline FuncType* ODRUseFunction(FuncType* ptr) {
  68. volatile FuncType* dummy = ptr;
  69. return dummy;
  70. }
  71. absl::Cord FlatCord(size_t size) {
  72. absl::Cord result(std::string(size, 'a'));
  73. result.Flatten();
  74. return result;
  75. }
  76. absl::Cord FragmentedCord(size_t size) {
  77. const size_t orig_size = size;
  78. std::vector<std::string> chunks;
  79. size_t chunk_size = std::max<size_t>(1, size / 10);
  80. while (size > chunk_size) {
  81. chunks.push_back(std::string(chunk_size, 'a'));
  82. size -= chunk_size;
  83. }
  84. if (size > 0) {
  85. chunks.push_back(std::string(size, 'a'));
  86. }
  87. absl::Cord result = absl::MakeFragmentedCord(chunks);
  88. (void) orig_size;
  89. assert(result.size() == orig_size);
  90. return result;
  91. }
  92. // Generates a benchmark and a codegen method for the provided types. The
  93. // codegen method provides a well known entrypoint for dumping assembly.
  94. #define MAKE_BENCHMARK(hash, name, ...) \
  95. namespace { \
  96. void BM_##hash##_##name(benchmark::State& state) { \
  97. RunBenchmark<hash>(state, __VA_ARGS__); \
  98. } \
  99. BENCHMARK(BM_##hash##_##name); \
  100. } \
  101. size_t Codegen##hash##name(const decltype(__VA_ARGS__)& arg); \
  102. size_t Codegen##hash##name(const decltype(__VA_ARGS__)& arg) { \
  103. return hash<decltype(__VA_ARGS__)>{}(arg); \
  104. } \
  105. bool absl_hash_test_odr_use##hash##name = \
  106. ODRUseFunction(&Codegen##hash##name);
  107. MAKE_BENCHMARK(AbslHash, Int32, int32_t{});
  108. MAKE_BENCHMARK(AbslHash, Int64, int64_t{});
  109. MAKE_BENCHMARK(AbslHash, Double, 1.2);
  110. MAKE_BENCHMARK(AbslHash, DoubleZero, 0.0);
  111. MAKE_BENCHMARK(AbslHash, PairInt32Int32, std::pair<int32_t, int32_t>{});
  112. MAKE_BENCHMARK(AbslHash, PairInt64Int64, std::pair<int64_t, int64_t>{});
  113. MAKE_BENCHMARK(AbslHash, TupleInt32BoolInt64,
  114. std::tuple<int32_t, bool, int64_t>{});
  115. MAKE_BENCHMARK(AbslHash, String_0, std::string());
  116. MAKE_BENCHMARK(AbslHash, String_10, std::string(10, 'a'));
  117. MAKE_BENCHMARK(AbslHash, String_30, std::string(30, 'a'));
  118. MAKE_BENCHMARK(AbslHash, String_90, std::string(90, 'a'));
  119. MAKE_BENCHMARK(AbslHash, String_200, std::string(200, 'a'));
  120. MAKE_BENCHMARK(AbslHash, String_5000, std::string(5000, 'a'));
  121. MAKE_BENCHMARK(AbslHash, Cord_Flat_0, absl::Cord());
  122. MAKE_BENCHMARK(AbslHash, Cord_Flat_10, FlatCord(10));
  123. MAKE_BENCHMARK(AbslHash, Cord_Flat_30, FlatCord(30));
  124. MAKE_BENCHMARK(AbslHash, Cord_Flat_90, FlatCord(90));
  125. MAKE_BENCHMARK(AbslHash, Cord_Flat_200, FlatCord(200));
  126. MAKE_BENCHMARK(AbslHash, Cord_Flat_5000, FlatCord(5000));
  127. MAKE_BENCHMARK(AbslHash, Cord_Fragmented_200, FragmentedCord(200));
  128. MAKE_BENCHMARK(AbslHash, Cord_Fragmented_5000, FragmentedCord(5000));
  129. MAKE_BENCHMARK(AbslHash, VectorInt64_10, std::vector<int64_t>(10));
  130. MAKE_BENCHMARK(AbslHash, VectorInt64_100, std::vector<int64_t>(100));
  131. MAKE_BENCHMARK(AbslHash, VectorDouble_10, std::vector<double>(10, 1.1));
  132. MAKE_BENCHMARK(AbslHash, VectorDouble_100, std::vector<double>(100, 1.1));
  133. MAKE_BENCHMARK(AbslHash, PairStringString_0,
  134. std::make_pair(std::string(), std::string()));
  135. MAKE_BENCHMARK(AbslHash, PairStringString_10,
  136. std::make_pair(std::string(10, 'a'), std::string(10, 'b')));
  137. MAKE_BENCHMARK(AbslHash, PairStringString_30,
  138. std::make_pair(std::string(30, 'a'), std::string(30, 'b')));
  139. MAKE_BENCHMARK(AbslHash, PairStringString_90,
  140. std::make_pair(std::string(90, 'a'), std::string(90, 'b')));
  141. MAKE_BENCHMARK(AbslHash, PairStringString_200,
  142. std::make_pair(std::string(200, 'a'), std::string(200, 'b')));
  143. MAKE_BENCHMARK(AbslHash, PairStringString_5000,
  144. std::make_pair(std::string(5000, 'a'), std::string(5000, 'b')));
  145. MAKE_BENCHMARK(TypeErasedAbslHash, Int32, int32_t{});
  146. MAKE_BENCHMARK(TypeErasedAbslHash, Int64, int64_t{});
  147. MAKE_BENCHMARK(TypeErasedAbslHash, PairInt32Int32,
  148. std::pair<int32_t, int32_t>{});
  149. MAKE_BENCHMARK(TypeErasedAbslHash, PairInt64Int64,
  150. std::pair<int64_t, int64_t>{});
  151. MAKE_BENCHMARK(TypeErasedAbslHash, TupleInt32BoolInt64,
  152. std::tuple<int32_t, bool, int64_t>{});
  153. MAKE_BENCHMARK(TypeErasedAbslHash, String_0, std::string());
  154. MAKE_BENCHMARK(TypeErasedAbslHash, String_10, std::string(10, 'a'));
  155. MAKE_BENCHMARK(TypeErasedAbslHash, String_30, std::string(30, 'a'));
  156. MAKE_BENCHMARK(TypeErasedAbslHash, String_90, std::string(90, 'a'));
  157. MAKE_BENCHMARK(TypeErasedAbslHash, String_200, std::string(200, 'a'));
  158. MAKE_BENCHMARK(TypeErasedAbslHash, String_5000, std::string(5000, 'a'));
  159. MAKE_BENCHMARK(TypeErasedAbslHash, VectorDouble_10,
  160. std::vector<double>(10, 1.1));
  161. MAKE_BENCHMARK(TypeErasedAbslHash, VectorDouble_100,
  162. std::vector<double>(100, 1.1));
  163. // The latency benchmark attempts to model the speed of the hash function in
  164. // production. When a hash function is used for hashtable lookups it is rarely
  165. // used to hash N items in a tight loop nor on constant sized strings. Instead,
  166. // after hashing there is a potential equality test plus a (usually) large
  167. // amount of user code. To simulate this effectively we introduce a data
  168. // dependency between elements we hash by using the hash of the Nth element as
  169. // the selector of the N+1th element to hash. This isolates the hash function
  170. // code much like in production. As a bonus we use the hash to generate strings
  171. // of size [1,N] (instead of fixed N) to disable perfect branch predictions in
  172. // hash function implementations.
  173. namespace {
  174. // 16kb fits in L1 cache of most CPUs we care about. Keeping memory latency low
  175. // will allow us to attribute most time to CPU which means more accurate
  176. // measurements.
  177. static constexpr size_t kEntropySize = 16 << 10;
  178. static char entropy[kEntropySize + 1024];
  179. ABSL_ATTRIBUTE_UNUSED static const bool kInitialized = [] {
  180. absl::BitGen gen;
  181. static_assert(sizeof(entropy) % sizeof(uint64_t) == 0, "");
  182. for (int i = 0; i != sizeof(entropy); i += sizeof(uint64_t)) {
  183. auto rand = absl::Uniform<uint64_t>(gen);
  184. memcpy(&entropy[i], &rand, sizeof(uint64_t));
  185. }
  186. return true;
  187. }();
  188. } // namespace
  189. template <class T>
  190. struct PodRand {
  191. static_assert(std::is_pod<T>::value, "");
  192. static_assert(kEntropySize + sizeof(T) < sizeof(entropy), "");
  193. T Get(size_t i) const {
  194. T v;
  195. memcpy(&v, &entropy[i % kEntropySize], sizeof(T));
  196. return v;
  197. }
  198. };
  199. template <size_t N>
  200. struct StringRand {
  201. static_assert(kEntropySize + N < sizeof(entropy), "");
  202. absl::string_view Get(size_t i) const {
  203. // This has a small bias towards small numbers. Because max N is ~200 this
  204. // is very small and prefer to be very fast instead of absolutely accurate.
  205. // Also we pass N = 2^K+1 so that mod reduces to a bitand.
  206. size_t s = (i % (N - 1)) + 1;
  207. return {&entropy[i % kEntropySize], s};
  208. }
  209. };
  210. #define MAKE_LATENCY_BENCHMARK(hash, name, ...) \
  211. namespace { \
  212. void BM_latency_##hash##_##name(benchmark::State& state) { \
  213. __VA_ARGS__ r; \
  214. hash<decltype(r.Get(0))> h; \
  215. size_t i = 871401241; \
  216. for (auto _ : state) { \
  217. benchmark::DoNotOptimize(i = h(r.Get(i))); \
  218. } \
  219. } \
  220. BENCHMARK(BM_latency_##hash##_##name); \
  221. } // namespace
  222. MAKE_LATENCY_BENCHMARK(AbslHash, Int32, PodRand<int32_t>);
  223. MAKE_LATENCY_BENCHMARK(AbslHash, Int64, PodRand<int64_t>);
  224. MAKE_LATENCY_BENCHMARK(AbslHash, String9, StringRand<9>);
  225. MAKE_LATENCY_BENCHMARK(AbslHash, String33, StringRand<33>);
  226. MAKE_LATENCY_BENCHMARK(AbslHash, String65, StringRand<65>);
  227. MAKE_LATENCY_BENCHMARK(AbslHash, String257, StringRand<257>);