mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
# Overview This is a latency-optimized hashing framework based on Abseil's and others. At it's core it uses both a normal 64-bit multiply as well as a 64-bit multiply capturing both low and high 64-bit components of the result and XOR-ing them together. These are the primitives used in FxHash and Abseil respectively, although they both appear in others. The implementation has been *substantially* optimized for short inputs and latency over quality. As a result, this function does not remotely pass the SMHasher quality tests. However, basic collisions are rare, and I've included a small subset of the SMHasher collision testing directly to make sure the quality doesn't slip too far inadvertently. The customization framework is roughly similar to Abseil's and LLVM's but has been simplified significantly, inspired in some respects by the AHash API design and in others by my experience of all performance sensitive hashing implementations needing to work at a very low level to hit their performance targets. The abstractions are stripped down to facilitate this. # Details of the performance optimization This function is 2x - 4x faster than LLVM's on small inputs, and up to 2x faster than Abseil. Significant effort has gone into optimizing short strings in particular compared to Abseil. Small integer and pointer hashing is also faster than Abseil's by leveraging a lower quality 64-bit multiply in some cases inspired by FxHash. One consequence is that this routine is particulary fast for 32-bit integers. The short string improvements largely come from packing more of the bytes of string into as few multiplies as possible. While this fails to mix the bits sufficient to hit SMHasher's strict avalanche criteria and does leave some collision windows, it provides dramatic latency improvements. Some of these techniques come from Abseil's own bulk hashing routine but re-applied here. Others are novel, for example using small sizes to sample nicely uniform random data to efficiently handle the very small number of bits of data that need to be hashed. The other observed improvement is diligent handling of pairs and tuples and fairly aggressively turning things into integers. Some of the comparisons with Abseil aren't realistic as the Abseil hash table does some of these mappings before hashing. I've done this directly in the hash function as that seems cleaner. For long strings, the performance is comparable or a bit better than Abseil, and significantly better than LLVM's hash function. Overall, for short inputs this is hoped to be the fastest hash function that still gets "just enough" mixing for modern hash tables to perform well. # Details of the quality vs. latency tradeoff A key insight is that modern hash tables don't need especially high quality hash functions, but do benefit from something beyond the identify function. That isn't the target of SMHasher or other quality assessing tools and has resulted in unnecessarily aggressive hashing for any functions actually evaluated against it. Many hash functions turn off the high quality implementations evaluated with SMHasher for integer or pointer keys to recover latency & performance (AHash for example), but the same performance-oriented design applies beyond these narrow types, for example for short strings. However, a consequence is that there are serious limits to the quality of the hash function. The avalanche test is failed hilariously, etc., but in the exact same ways as Abseil itself fails it for integer keys. There are also real collisions spaces. For example, for 16-byte strings, there is one 64-bit value for the first 8 bytes that will have the same hash regardless of the other 8 bytes of the string. Some minor effort is taken to make this pattern unlikely to be a practical problem, but it is a clear theoretical weakness. It also means that this hash function couldn't be further from providing any hash-flooding DoS attack protection -- I expect it to be trivially easy to attack in this way by a motivated adversary. Defending against these attacks is defined as out-of-scope, in large part because even attempts that have made a compelling effort to address these issues such as HighwayHash have found serious limits. Instead, this takes a principled position that any such defense should be provided entirely at the data structure level with a strong worst-case bound rather than through strengthening the hash function. # Future work A subsequent PR will introduce a hash table inspired very heavily by the design of Abseil's "SwissTable" and using this hash function. The goal is to provide a significant improvement to hot hash tables such as the identifier table in the lexer of Carbon's toolchain. # Detailed benchmark data The benchmarks introduced are heavily inspired by the latency benchmarking of hash functions in Abseil. I've adapted them to fit better into Carbon's coding style and to try to have more stable results with broader coverage of types and string sizes. Running the benchmarks directly gives horizontal comparisons across different hash functions. That can be hard to read, so here is *just* the newly introduced hash function benchmark results on an AMD server: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 3.11ns ± 1% BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 3.11ns ± 1% BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 4.11ns ± 1% BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 3.12ns ± 1% BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 4.13ns ± 1% BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 3.16ns ± 2% BM_LatencyHash<RandValues<int*>, CarbonHashBench> 3.16ns ± 2% BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 4.03ns ± 2% BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 4.04ns ± 1% BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 4.34ns ± 2% BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 4.04ns ± 1% BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 4.34ns ± 2% BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 4.33ns ± 1% BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 4.33ns ± 1% BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 4.34ns ± 1% BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 4.33ns ± 1% BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 4.33ns ± 1% BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 1.95ns ± 4% BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 1.70ns ± 3% BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 3.52ns ± 3% BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 4.46ns ± 2% BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 7.69ns ± 1% BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 14.8ns ± 1% BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 21.5ns ± 1% BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 34.6ns ± 0% BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 63.1ns ± 1% BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 118ns ± 1% BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 225ns ± 1% ``` And on an ARM server: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 5.28ns ± 0% BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 5.29ns ± 0% BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 7.02ns ± 0% BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 5.34ns ± 1% BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 7.07ns ± 4% BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 5.36ns ± 2% BM_LatencyHash<RandValues<int*>, CarbonHashBench> 5.36ns ± 2% BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 7.19ns ± 3% BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 7.29ns ± 2% BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 7.31ns ± 4% BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 7.29ns ± 2% BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 7.31ns ± 4% BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 8.69ns ± 3% BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 8.69ns ± 3% BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 8.69ns ± 3% BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 8.69ns ± 3% BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 8.69ns ± 3% BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 2.64ns ± 2% BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 2.90ns ± 4% BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 6.14ns ± 1% BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 8.27ns ± 1% BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 13.8ns ± 0% BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 31.2ns ± 0% BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 49.9ns ± 0% BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 86.9ns ± 0% BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 163ns ± 0% BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 312ns ± 0% BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 610ns ± 0% ``` I don't have the same nice statistical multi-run error bars, but one run from my M1 MacBook: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 3.89 ns BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 3.87 ns BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 4.39 ns BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 3.93 ns BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 4.98 ns BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 3.87 ns BM_LatencyHash<RandValues<int*>, CarbonHashBench> 3.87 ns BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 4.86 ns BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 4.43 ns BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 4.41 ns BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 4.44 ns BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 4.69 ns BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 4.33 ns BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 4.38 ns BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 4.34 ns BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 4.35 ns BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 4.38 ns BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 1.15 ns BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 0.973 ns BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 3.03 ns BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 3.97 ns BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 6.64 ns BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 12.5 ns BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 17.9 ns BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 27.9 ns BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 48.1 ns BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 87.3 ns BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 166 ns ``` And here I have internally replaced the Carbon hash function with Abseil's hash function for "before" and then restored it in the "after" and computed the delta for each benchmark. This basically shows the speed-up (lower time -> lower latency -> speed-up -> good) over Abseil on an AMD server: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 4.00ns ± 1% 3.10ns ± 0% -22.45% (p=0.000 n=20+15) BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 4.01ns ± 1% 3.10ns ± 1% -22.64% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 6.25ns ± 1% 4.10ns ± 1% -34.30% (p=0.000 n=20+20) BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 4.02ns ± 1% 3.12ns ± 1% -22.50% (p=0.000 n=19+19) BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 6.25ns ± 1% 4.11ns ± 1% -34.20% (p=0.000 n=20+19) BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 4.03ns ± 1% 3.14ns ± 1% -22.17% (p=0.000 n=19+19) BM_LatencyHash<RandValues<int*>, CarbonHashBench> 5.95ns ± 1% 3.14ns ± 1% -47.24% (p=0.000 n=20+18) BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 6.04ns ± 1% 4.01ns ± 1% -33.64% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 5.96ns ± 1% 4.02ns ± 1% -32.51% (p=0.000 n=18+20) BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 5.93ns ± 1% 4.30ns ± 1% -27.56% (p=0.000 n=20+17) BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 7.97ns ± 1% 4.02ns ± 1% -49.50% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 7.98ns ± 1% 4.32ns ± 1% -45.88% (p=0.000 n=19+20) BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 4.40ns ± 2% 4.32ns ± 1% -1.81% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 5.94ns ± 1% 4.32ns ± 1% -27.25% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 10.0ns ± 1% 4.3ns ± 1% -56.56% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 8.04ns ± 1% 4.32ns ± 1% -46.29% (p=0.000 n=20+19) BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 7.95ns ± 1% 4.33ns ± 1% -45.59% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 3.28ns ± 3% 1.93ns ± 4% -41.19% (p=0.000 n=18+20) BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 3.05ns ± 3% 1.69ns ± 4% -44.52% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 5.88ns ± 2% 3.50ns ± 3% -40.42% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 8.92ns ± 1% 4.44ns ± 2% -50.22% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 12.0ns ± 1% 7.7ns ± 1% -36.16% (p=0.000 n=18+20) BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 18.8ns ± 0% 14.7ns ± 1% -21.73% (p=0.000 n=17+20) BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 25.5ns ± 1% 21.4ns ± 1% -16.18% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 38.7ns ± 2% 34.5ns ± 1% -10.78% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 69.7ns ± 1% 62.8ns ± 1% -9.88% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 130ns ± 1% 117ns ± 1% -9.45% (p=0.000 n=20+19) BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 244ns ± 0% 225ns ± 1% -8.11% (p=0.000 n=17+20) ``` ... and on an ARM server: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 6.48ns ± 1% 5.28ns ± 0% -18.62% (p=0.000 n=20+20) BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 7.40ns ± 1% 5.29ns ± 1% -28.45% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 10.4ns ± 0% 7.0ns ± 0% -32.34% (p=0.000 n=19+20) BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 6.56ns ± 1% 5.32ns ± 1% -18.95% (p=0.000 n=20+19) BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 10.8ns ± 2% 7.0ns ± 1% -34.89% (p=0.000 n=20+20) BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 6.71ns ± 3% 5.38ns ± 2% -19.84% (p=0.000 n=20+20) BM_LatencyHash<RandValues<int*>, CarbonHashBench> 10.3ns ± 3% 5.4ns ± 2% -47.67% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 10.9ns ± 2% 7.2ns ± 4% -33.67% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 10.7ns ± 4% 7.3ns ± 4% -31.66% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 10.5ns ± 3% 7.3ns ± 4% -30.71% (p=0.000 n=20+19) BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 14.1ns ± 3% 7.3ns ± 4% -48.32% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 14.0ns ± 1% 7.3ns ± 4% -47.95% (p=0.000 n=19+19) BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 9.41ns ± 4% 8.68ns ± 4% -7.71% (p=0.000 n=19+19) BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 12.2ns ± 2% 8.7ns ± 4% -28.81% (p=0.000 n=18+19) BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 18.9ns ± 2% 8.7ns ± 4% -54.17% (p=0.000 n=17+19) BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 15.6ns ± 2% 8.7ns ± 4% -44.37% (p=0.000 n=17+19) BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 15.5ns ± 2% 8.7ns ± 4% -44.08% (p=0.000 n=18+19) BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 5.89ns ± 2% 2.64ns ± 3% -55.26% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 5.73ns ± 3% 2.88ns ± 3% -49.71% (p=0.000 n=18+20) BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 10.1ns ± 1% 6.1ns ± 2% -39.00% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 15.7ns ± 0% 8.3ns ± 1% -47.27% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 21.2ns ± 0% 13.8ns ± 0% -34.81% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 37.9ns ± 0% 31.2ns ± 0% -17.77% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 56.8ns ± 0% 49.8ns ± 0% -12.21% (p=0.000 n=20+18) BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 93.8ns ± 0% 86.9ns ± 0% -7.38% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 174ns ± 0% 163ns ± 0% -6.03% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 330ns ± 0% 312ns ± 0% -5.25% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 641ns ± 0% 610ns ± 0% -4.79% (p=0.000 n=19+19) ``` This is the same as the above delta comparison, but with the "before" being LLVM's hash function: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 6.85ns ± 1% 3.10ns ± 1% -54.78% (p=0.000 n=20+19) BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 6.85ns ± 1% 3.10ns ± 1% -54.78% (p=0.000 n=20+19) BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 6.25ns ± 1% 4.09ns ± 1% -34.58% (p=0.000 n=20+20) BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 6.87ns ± 1% 3.12ns ± 2% -54.66% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 7.35ns ± 1% 4.10ns ± 1% -44.20% (p=0.000 n=20+19) BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 7.34ns ± 1% 3.13ns ± 1% -57.34% (p=0.000 n=20+18) BM_LatencyHash<RandValues<int*>, CarbonHashBench> 7.33ns ± 1% 3.13ns ± 2% -57.27% (p=0.000 n=20+18) BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 7.27ns ± 1% 3.99ns ± 1% -45.12% (p=0.000 n=20+18) BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 14.5ns ± 1% 4.0ns ± 1% -72.23% (p=0.000 n=19+19) BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 14.6ns ± 1% 4.3ns ± 2% -70.44% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 14.5ns ± 1% 4.0ns ± 1% -72.21% (p=0.000 n=20+19) BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 14.6ns ± 1% 4.3ns ± 1% -70.46% (p=0.000 n=20+18) BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 7.31ns ± 1% 4.33ns ± 1% -40.81% (p=0.000 n=18+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 7.78ns ± 1% 4.32ns ± 1% -44.45% (p=0.000 n=18+20) BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 7.78ns ± 2% 4.33ns ± 1% -44.42% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 7.62ns ± 1% 4.32ns ± 1% -43.24% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 7.77ns ± 1% 4.33ns ± 1% -44.34% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 8.15ns ± 3% 1.94ns ± 5% -76.16% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 7.02ns ± 3% 1.69ns ± 4% -75.94% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 7.83ns ± 2% 3.50ns ± 3% -55.34% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 9.17ns ± 1% 4.43ns ± 2% -51.65% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 11.3ns ± 1% 7.6ns ± 1% -32.04% (p=0.000 n=20+19) BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 23.0ns ± 1% 14.7ns ± 1% -36.14% (p=0.000 n=20+19) BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 32.9ns ± 0% 21.4ns ± 1% -34.96% (p=0.000 n=17+19) BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 52.2ns ± 1% 34.4ns ± 1% -34.01% (p=0.000 n=19+18) BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 92.1ns ± 1% 62.8ns ± 1% -31.82% (p=0.000 n=19+19) BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 169ns ± 1% 117ns ± 1% -30.53% (p=0.000 n=20+19) BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 319ns ± 1% 224ns ± 1% -29.78% (p=0.000 n=20+18) ``` ... and on an ARM server: ``` BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench> 8.38ns ± 0% 5.27ns ± 0% -37.04% (p=0.000 n=20+20) BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> 8.39ns ± 1% 5.28ns ± 0% -37.01% (p=0.000 n=19+19) BM_LatencyHash<RandValues<std::pair<uint8_t, uint8_t>>, CarbonHashBench> 8.07ns ± 0% 7.02ns ± 0% -13.10% (p=0.000 n=19+20) BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> 8.48ns ± 1% 5.32ns ± 1% -37.25% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint16_t, uint16_t>>, CarbonHashBench> 9.34ns ± 2% 7.09ns ± 2% -24.14% (p=0.000 n=19+20) BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> 9.76ns ± 3% 5.37ns ± 2% -44.98% (p=0.000 n=20+20) BM_LatencyHash<RandValues<int*>, CarbonHashBench> 9.76ns ± 3% 5.37ns ± 2% -44.98% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> 10.1ns ± 2% 7.2ns ± 3% -29.36% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint32_t>>, CarbonHashBench> 11.9ns ± 2% 7.3ns ± 4% -38.68% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> 11.3ns ± 2% 7.3ns ± 4% -35.16% (p=0.000 n=19+19) BM_LatencyHash<RandValues<std::pair<int*, uint32_t>>, CarbonHashBench> 11.9ns ± 2% 7.3ns ± 4% -38.68% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench> 11.3ns ± 2% 7.3ns ± 4% -35.16% (p=0.000 n=19+19) BM_LatencyHash<RandValues<__uint128_t>, CarbonHashBench> 10.3ns ± 2% 8.7ns ± 3% -15.81% (p=0.000 n=19+20) BM_LatencyHash<RandValues<std::pair<uint64_t, uint64_t>>, CarbonHashBench> 11.6ns ± 3% 8.7ns ± 3% -25.44% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<int*, int*>>, CarbonHashBench> 11.6ns ± 3% 8.7ns ± 3% -25.44% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<uint64_t, int*>>, CarbonHashBench> 11.6ns ± 3% 8.7ns ± 3% -25.44% (p=0.000 n=20+20) BM_LatencyHash<RandValues<std::pair<int*, uint64_t>>, CarbonHashBench> 11.6ns ± 3% 8.7ns ± 3% -25.44% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 4>, CarbonHashBench> 9.39ns ± 2% 2.66ns ± 3% -71.66% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 8>, CarbonHashBench> 10.7ns ± 3% 2.9ns ± 3% -72.97% (p=0.000 n=19+18) BM_LatencyHash<RandStrings< true, 16>, CarbonHashBench> 11.8ns ± 1% 6.1ns ± 2% -47.75% (p=0.000 n=19+20) BM_LatencyHash<RandStrings< true, 32>, CarbonHashBench> 13.9ns ± 1% 8.3ns ± 1% -40.71% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 64>, CarbonHashBench> 16.8ns ± 1% 13.8ns ± 0% -17.83% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 256>, CarbonHashBench> 31.7ns ± 1% 31.2ns ± 0% -1.76% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 512>, CarbonHashBench> 43.5ns ± 0% 49.8ns ± 0% +14.56% (p=0.000 n=18+20) BM_LatencyHash<RandStrings< true, 1024>, CarbonHashBench> 66.2ns ± 0% 86.9ns ± 0% +31.39% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 2048>, CarbonHashBench> 112ns ± 0% 163ns ± 0% +46.09% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 4096>, CarbonHashBench> 201ns ± 0% 312ns ± 0% +55.49% (p=0.000 n=20+20) BM_LatencyHash<RandStrings< true, 8192>, CarbonHashBench> 379ns ± 0% 610ns ± 0% +61.08% (p=0.000 n=20+20) ``` Note that there is a significant regression on long strings compared to LLVM's hash function on the ARM server I have access to. This doesn't show up on the M1 at all, and is likely specific to inadequate throughput for the 64-bit multiply operations. This seems fine as a) our priority is for short strings, and b) the M1 and other ARM CPUs are likely to improve here over time given the prevalent use of this core technique. For example, Abseil's current hash algorithm has the same long-string behavior (and performance bottleneck) on this server. --------- Co-authored-by: josh11b <josh11b@users.noreply.github.com> Co-authored-by: Geoff Romer <gromer@google.com>
302 lines
13 KiB
C++
302 lines
13 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include <benchmark/benchmark.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
|
|
#include "absl/hash/hash.h"
|
|
#include "absl/random/random.h"
|
|
#include "common/hashing.h"
|
|
#include "llvm/ADT/Hashing.h"
|
|
|
|
namespace Carbon {
|
|
namespace {
|
|
|
|
// We want the benchmark working set to fit in the L1 cache where possible so
|
|
// that the benchmark focuses on the CPU-execution costs and not memory latency.
|
|
// For most CPUs we're going to care about, 16k will fit easily, and 32k will
|
|
// probably fit. But we also need to include sizes for string benchmarks. This
|
|
// targets 8k of entropy with each object up to 8k of size for a total of 16k.
|
|
constexpr int EntropySize = 8 << 10;
|
|
constexpr int EntropyObjSize = 8 << 10;
|
|
|
|
// An array of random entropy with `EntropySize` bytes plus 8k. The goal is that
|
|
// clients can read `EntropySize` objects of up to 8k size out of this pool by
|
|
// starting at different byte offsets.
|
|
static const llvm::ArrayRef<std::byte> entropy_bytes =
|
|
[]() -> llvm::ArrayRef<std::byte> {
|
|
static llvm::SmallVector<std::byte> bytes;
|
|
// Pad out the entropy for up to 1kb objects.
|
|
bytes.resize(EntropySize + EntropyObjSize);
|
|
absl::BitGen gen;
|
|
for (std::byte& b : bytes) {
|
|
b = static_cast<std::byte>(absl::Uniform<uint8_t>(gen));
|
|
}
|
|
return bytes;
|
|
}();
|
|
|
|
// Based on 16k of entropy above and an L1 cache size often up to 32k, keep each
|
|
// array of sizes small at 8k or 1k 8-byte sizes.
|
|
constexpr int NumSizes = 1 << 10;
|
|
|
|
// Selects an array of `NumSizes` sizes, witch each one in the range [0,
|
|
// MaxSize). The sizes will be in a random order, but the sum of sizes will
|
|
// always be the same.
|
|
template <size_t MaxSize>
|
|
static const std::array<size_t, NumSizes> rand_sizes = []() {
|
|
std::array<size_t, NumSizes> sizes;
|
|
// Build an array with a deterministic set of sizes in the
|
|
// range [0, MaxSize), using the golden ratio to select well distributed
|
|
// points in that range. See https://www.youtube.com/watch?v=lOIP_Z_-0Hs for
|
|
// an example of why this is an effective strategy for selecting sizes in the
|
|
// range.
|
|
static_assert(NumSizes > 128);
|
|
constexpr double Phi = 1.61803398875;
|
|
constexpr size_t Scale = std::max<size_t>(1, MaxSize / Phi);
|
|
for (auto [i, size] : llvm::enumerate(sizes)) {
|
|
size = (i * Scale) % MaxSize;
|
|
}
|
|
// Shuffle the sizes randomly so that there isn't any pattern of sizes
|
|
// encountered and we get relatively realistic branch prediction behavior
|
|
// when branching on the size. We use this approach rather than random
|
|
// sizes to ensure we always have the same total size of data processed.
|
|
std::shuffle(sizes.begin(), sizes.end(), absl::BitGen());
|
|
return sizes;
|
|
}();
|
|
|
|
// A small helper class to synthesize random values out of our entropy pool.
|
|
// This is done in a way that depends on an arbitrary input (`x`) to allow us to
|
|
// create a benchmark that measures a *dependent* chain of hashes of these
|
|
// values.
|
|
//
|
|
// `T` needs to be default constructable and reasonable to synthesize an
|
|
// instance by copying random bytes into its underlying storage.
|
|
//
|
|
// This helper class also accumulates the number of bytes of data generated in
|
|
// order to let us compute throughput measurements as well as latency
|
|
// measurements.
|
|
//
|
|
// This helper class has the same API as the `RandStrings` helpers below so that
|
|
// they can all be used as type parameters to a common benchmark routine below.
|
|
template <typename T>
|
|
struct RandValues {
|
|
size_t bytes = 0;
|
|
|
|
// Get a random value. We don't need to iterate through sizes so `i` is
|
|
// ignored, but we use `x` to select our entropy ensuring a dependency on `x`
|
|
// for the benchmark.
|
|
auto Get(ssize_t /*i*/, uint64_t x) -> T {
|
|
static_assert(sizeof(T) <= EntropyObjSize);
|
|
bytes += sizeof(T);
|
|
T result;
|
|
memcpy(&result, &entropy_bytes[x % EntropySize], sizeof(T));
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// A specialization to help with building pairs of values.
|
|
template <typename T, typename U>
|
|
struct RandValues<std::pair<T, U>> {
|
|
size_t bytes = 0;
|
|
|
|
auto Get(ssize_t /*i*/, uint64_t x) -> std::pair<T, U> {
|
|
static_assert(sizeof(std::pair<T, U>) <= EntropyObjSize);
|
|
bytes += sizeof(std::pair<T, U>);
|
|
T result0;
|
|
U result1;
|
|
memcpy(&result0, &entropy_bytes[x % EntropySize], sizeof(T));
|
|
memcpy(&result1, &entropy_bytes[x % EntropySize] + sizeof(T), sizeof(U));
|
|
return {result0, result1};
|
|
}
|
|
};
|
|
|
|
// A helper class similar to `RandValues`, but for building strings rather than
|
|
// values. The string content is pulled from the entropy pool. The size can be
|
|
// random from [0, MaxSize], or it can be fixed at `MaxSize`. But the `MaxSize`
|
|
// cannot be larger than a single byte sequence pulled from the entropy pool
|
|
// (`EntropyObjSize`).
|
|
template <bool RandSize, size_t MaxSize>
|
|
struct RandStrings {
|
|
size_t bytes = 0;
|
|
|
|
// Get a random string. If the sizes are random, we use `i` to select each
|
|
// size and require it to be in the range [0, NumSizes). Otherwise `i` is
|
|
// ignored. We always use `x` to select the entropy and establish a dependency
|
|
// on the input.
|
|
auto Get(ssize_t i, uint64_t x) -> llvm::StringRef {
|
|
static_assert(MaxSize <= EntropyObjSize);
|
|
size_t s = MaxSize;
|
|
if constexpr (RandSize) {
|
|
// When using random sizes, we leverage `i` which is guaranteed to range
|
|
// from [0, NumSizes).
|
|
s = rand_sizes<MaxSize>[i];
|
|
} else {
|
|
// Prevent `s` from being constant folded when we directly use `MaxSize`.
|
|
benchmark::DoNotOptimize(s);
|
|
}
|
|
bytes += s;
|
|
return llvm::StringRef(
|
|
reinterpret_cast<const char*>(&entropy_bytes[x % EntropySize]), s);
|
|
}
|
|
};
|
|
|
|
struct HashBenchBase {
|
|
uint64_t seed;
|
|
|
|
HashBenchBase() {
|
|
// The real-world use case we care about is in a hash table where we'll mix
|
|
// in some seed state, likely some ASLR address. To simulate this for
|
|
// benchmarking, compute a seed from the address of a stack local variable.
|
|
volatile char key;
|
|
key = 42;
|
|
// Rinse this through a volatile variable as well so returning it isn't
|
|
// flagged. The whole point is to escape the address of something on the
|
|
// stack.
|
|
volatile auto key_addr = reinterpret_cast<uint64_t>(&key);
|
|
seed = key_addr;
|
|
}
|
|
};
|
|
|
|
struct CarbonHashBench : HashBenchBase {
|
|
template <typename T>
|
|
auto operator()(const T& value) -> uint64_t {
|
|
return static_cast<uint64_t>(HashValue(value, seed));
|
|
}
|
|
};
|
|
|
|
struct AbseilHashBench : HashBenchBase {
|
|
template <typename T>
|
|
auto operator()(const T& value) -> uint64_t {
|
|
// Manually seed this with an after-the-fact XOR as there isn't a seeded
|
|
// version. This matches what Abseil's hash tables do as well.
|
|
return absl::HashOf(value) ^ seed;
|
|
}
|
|
};
|
|
|
|
struct LLVMHashBench : HashBenchBase {
|
|
template <typename T>
|
|
auto operator()(const T& value) -> uint64_t {
|
|
// Manually seed this with an after-the-fact XOR as there isn't a seeded
|
|
// version.
|
|
return llvm::hash_value(value) ^ seed;
|
|
}
|
|
};
|
|
|
|
template <typename Values, typename Hasher>
|
|
void BM_LatencyHash(benchmark::State& state) {
|
|
uint64_t x = 13;
|
|
Values v;
|
|
Hasher h;
|
|
// We run the benchmark in `NumSizes` batches so that when needed we always
|
|
// process each of the sizes and we don't randomly end up with a skewed set of
|
|
// sizes.
|
|
while (state.KeepRunningBatch(NumSizes)) {
|
|
for (ssize_t i = 0; i < NumSizes; ++i) {
|
|
benchmark::DoNotOptimize(x = h(v.Get(i, x)));
|
|
}
|
|
}
|
|
state.SetBytesProcessed(v.bytes);
|
|
}
|
|
|
|
// Latency benchmarks are grouped by the three different hash functions to
|
|
// facilitate comparing their performance for a given value type or string size
|
|
// bucket.
|
|
#define LATENCY_VALUE_BENCHMARKS(...) \
|
|
BENCHMARK(BM_LatencyHash<RandValues<__VA_ARGS__>, CarbonHashBench>); \
|
|
BENCHMARK(BM_LatencyHash<RandValues<__VA_ARGS__>, AbseilHashBench>); \
|
|
BENCHMARK(BM_LatencyHash<RandValues<__VA_ARGS__>, LLVMHashBench>)
|
|
LATENCY_VALUE_BENCHMARKS(uint8_t);
|
|
LATENCY_VALUE_BENCHMARKS(uint16_t);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint8_t, uint8_t>);
|
|
LATENCY_VALUE_BENCHMARKS(uint32_t);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint16_t, uint16_t>);
|
|
LATENCY_VALUE_BENCHMARKS(uint64_t);
|
|
LATENCY_VALUE_BENCHMARKS(int*);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint32_t, uint32_t>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint64_t, uint32_t>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint32_t, uint64_t>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<int*, uint32_t>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint32_t, int*>);
|
|
LATENCY_VALUE_BENCHMARKS(__uint128_t);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint64_t, uint64_t>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<int*, int*>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<uint64_t, int*>);
|
|
LATENCY_VALUE_BENCHMARKS(std::pair<int*, uint64_t>);
|
|
|
|
#define LATENCY_STRING_BENCHMARKS(MaxSize) \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/true, MaxSize>, \
|
|
CarbonHashBench>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/true, MaxSize>, \
|
|
AbseilHashBench>); \
|
|
BENCHMARK( \
|
|
BM_LatencyHash<RandStrings</*RandSize=*/true, MaxSize>, LLVMHashBench>)
|
|
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/4);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/8);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/16);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/32);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/64);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/256);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/512);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/1024);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/2048);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/4096);
|
|
LATENCY_STRING_BENCHMARKS(/*MaxSize=*/8192);
|
|
|
|
// We also want to check for size-specific cliffs, particularly in small sizes
|
|
// and sizes around implementation inflection points such as powers of two and
|
|
// half-way points between powers of two. Because these benchmarks are looking
|
|
// for size-related cliffs, all the runs for particular hash function are kept
|
|
// together.
|
|
//
|
|
// Note: because these use a fixed size, their specific timing isn't terribly
|
|
// informative. The branch predictor behavior on a modern CPU will be
|
|
// significantly different in this benchmarks from any other and may distort all
|
|
// manner of the timings. The results should really only be compared between
|
|
// sizes for cliffs, and not directly compared with other numbers.
|
|
#define LATENCY_STRING_SIZE_BENCHMARKS(Hash) \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 0>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 1>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 2>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 3>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 4>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 5>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 6>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 7>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 8>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 9>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 15>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 16>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 17>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 23>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 24>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 25>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 31>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 32>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 33>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 47>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 48>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 49>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 63>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 64>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 65>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 91>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 92>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 93>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 127>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 128>, Hash>); \
|
|
BENCHMARK(BM_LatencyHash<RandStrings</*RandSize=*/false, 129>, Hash>)
|
|
|
|
// Because these just look for size-related cliffs in performance, we only do a
|
|
// minimal number of benchmarks. There are a lot of sizes so this avoids wasted
|
|
// time in benchmark runs and there isn't much value from greater comparative
|
|
// coverage here.
|
|
LATENCY_STRING_SIZE_BENCHMARKS(CarbonHashBench);
|
|
LATENCY_STRING_SIZE_BENCHMARKS(AbseilHashBench);
|
|
|
|
} // namespace
|
|
} // namespace Carbon
|