| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 | // Copyright 2018 The Abseil Authors.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////      http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#include "absl/strings/string_view.h"#include <algorithm>#include <cstdint>#include <map>#include <random>#include <string>#include <unordered_set>#include <vector>#include "benchmark/benchmark.h"#include "absl/base/attributes.h"#include "absl/base/internal/raw_logging.h"#include "absl/base/macros.h"#include "absl/strings/str_cat.h"namespace {// Provide a forcibly out-of-line wrapper for operator== that can be used in// benchmarks to measure the impact of inlining.ABSL_ATTRIBUTE_NOINLINEbool NonInlinedEq(absl::string_view a, absl::string_view b) { return a == b; }// We use functions that cannot be inlined to perform the comparison loops so// that inlining of the operator== can't optimize away *everything*.ABSL_ATTRIBUTE_NOINLINEvoid DoEqualityComparisons(benchmark::State& state, absl::string_view a,                           absl::string_view b) {  for (auto _ : state) {    benchmark::DoNotOptimize(a == b);  }}void BM_EqualIdentical(benchmark::State& state) {  std::string x(state.range(0), 'a');  DoEqualityComparisons(state, x, x);}BENCHMARK(BM_EqualIdentical)->DenseRange(0, 3)->Range(4, 1 << 10);void BM_EqualSame(benchmark::State& state) {  std::string x(state.range(0), 'a');  std::string y = x;  DoEqualityComparisons(state, x, y);}BENCHMARK(BM_EqualSame)    ->DenseRange(0, 10)    ->Arg(20)    ->Arg(40)    ->Arg(70)    ->Arg(110)    ->Range(160, 4096);void BM_EqualDifferent(benchmark::State& state) {  const int len = state.range(0);  std::string x(len, 'a');  std::string y = x;  if (len > 0) {    y[len - 1] = 'b';  }  DoEqualityComparisons(state, x, y);}BENCHMARK(BM_EqualDifferent)->DenseRange(0, 3)->Range(4, 1 << 10);// This benchmark is intended to check that important simplifications can be// made with absl::string_view comparisons against constant strings. The idea is// that if constant strings cause redundant components of the comparison, the// compiler should detect and eliminate them. Here we use 8 different strings,// each with the same size. Provided our comparison makes the implementation// inline-able by the compiler, it should fold all of these away into a single// size check once per loop iteration.ABSL_ATTRIBUTE_NOINLINEvoid DoConstantSizeInlinedEqualityComparisons(benchmark::State& state,                                              absl::string_view a) {  for (auto _ : state) {    benchmark::DoNotOptimize(a == "aaa");    benchmark::DoNotOptimize(a == "bbb");    benchmark::DoNotOptimize(a == "ccc");    benchmark::DoNotOptimize(a == "ddd");    benchmark::DoNotOptimize(a == "eee");    benchmark::DoNotOptimize(a == "fff");    benchmark::DoNotOptimize(a == "ggg");    benchmark::DoNotOptimize(a == "hhh");  }}void BM_EqualConstantSizeInlined(benchmark::State& state) {  std::string x(state.range(0), 'a');  DoConstantSizeInlinedEqualityComparisons(state, x);}// We only need to check for size of 3, and <> 3 as this benchmark only has to// do with size differences.BENCHMARK(BM_EqualConstantSizeInlined)->DenseRange(2, 4);// This benchmark exists purely to give context to the above timings: this is// what they would look like if the compiler is completely unable to simplify// between two comparisons when they are comparing against constant strings.ABSL_ATTRIBUTE_NOINLINEvoid DoConstantSizeNonInlinedEqualityComparisons(benchmark::State& state,                                                 absl::string_view a) {  for (auto _ : state) {    // Force these out-of-line to compare with the above function.    benchmark::DoNotOptimize(NonInlinedEq(a, "aaa"));    benchmark::DoNotOptimize(NonInlinedEq(a, "bbb"));    benchmark::DoNotOptimize(NonInlinedEq(a, "ccc"));    benchmark::DoNotOptimize(NonInlinedEq(a, "ddd"));    benchmark::DoNotOptimize(NonInlinedEq(a, "eee"));    benchmark::DoNotOptimize(NonInlinedEq(a, "fff"));    benchmark::DoNotOptimize(NonInlinedEq(a, "ggg"));    benchmark::DoNotOptimize(NonInlinedEq(a, "hhh"));  }}void BM_EqualConstantSizeNonInlined(benchmark::State& state) {  std::string x(state.range(0), 'a');  DoConstantSizeNonInlinedEqualityComparisons(state, x);}// We only need to check for size of 3, and <> 3 as this benchmark only has to// do with size differences.BENCHMARK(BM_EqualConstantSizeNonInlined)->DenseRange(2, 4);void BM_CompareSame(benchmark::State& state) {  const int len = state.range(0);  std::string x;  for (int i = 0; i < len; i++) {    x += 'a';  }  std::string y = x;  absl::string_view a = x;  absl::string_view b = y;  for (auto _ : state) {    benchmark::DoNotOptimize(a.compare(b));  }}BENCHMARK(BM_CompareSame)->DenseRange(0, 3)->Range(4, 1 << 10);void BM_find_string_view_len_one(benchmark::State& state) {  std::string haystack(state.range(0), '0');  absl::string_view s(haystack);  for (auto _ : state) {    s.find("x");  // not present; length 1  }}BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20);void BM_find_string_view_len_two(benchmark::State& state) {  std::string haystack(state.range(0), '0');  absl::string_view s(haystack);  for (auto _ : state) {    s.find("xx");  // not present; length 2  }}BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20);void BM_find_one_char(benchmark::State& state) {  std::string haystack(state.range(0), '0');  absl::string_view s(haystack);  for (auto _ : state) {    s.find('x');  // not present  }}BENCHMARK(BM_find_one_char)->Range(1, 1 << 20);void BM_rfind_one_char(benchmark::State& state) {  std::string haystack(state.range(0), '0');  absl::string_view s(haystack);  for (auto _ : state) {    s.rfind('x');  // not present  }}BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20);void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) {  const int needle_len = state.range(0);  std::string needle;  for (int i = 0; i < needle_len; ++i) {    needle += 'a' + i;  }  std::string haystack(haystack_len, '0');  // 1000 zeros.  absl::string_view s(haystack);  for (auto _ : state) {    s.find_first_of(needle);  }}void BM_find_first_of_short(benchmark::State& state) {  BM_worst_case_find_first_of(state, 10);}void BM_find_first_of_medium(benchmark::State& state) {  BM_worst_case_find_first_of(state, 100);}void BM_find_first_of_long(benchmark::State& state) {  BM_worst_case_find_first_of(state, 1000);}BENCHMARK(BM_find_first_of_short)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);BENCHMARK(BM_find_first_of_medium)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);BENCHMARK(BM_find_first_of_long)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);struct EasyMap : public std::map<absl::string_view, uint64_t> {  explicit EasyMap(size_t) {}};// This templated benchmark helper function is intended to stress operator== or// operator< in a realistic test.  It surely isn't entirely realistic, but it's// a start.  The test creates a map of type Map, a template arg, and populates// it with table_size key/value pairs. Each key has WordsPerKey words.  After// creating the map, a number of lookups are done in random order.  Some keys// are used much more frequently than others in this phase of the test.template <typename Map, int WordsPerKey>void StringViewMapBenchmark(benchmark::State& state) {  const int table_size = state.range(0);  const double kFractionOfKeysThatAreHot = 0.2;  const int kNumLookupsOfHotKeys = 20;  const int kNumLookupsOfColdKeys = 1;  const char* words[] = {"the",   "quick",  "brown",    "fox",      "jumped",                         "over",  "the",    "lazy",     "dog",      "and",                         "found", "a",      "large",    "mushroom", "and",                         "a",     "couple", "crickets", "eating",   "pie"};  // Create some keys that consist of words in random order.  std::random_device r;  std::seed_seq seed({r(), r(), r(), r(), r(), r(), r(), r()});  std::mt19937 rng(seed);  std::vector<std::string> keys(table_size);  std::vector<int> all_indices;  const int kBlockSize = 1 << 12;  std::unordered_set<std::string> t(kBlockSize);  std::uniform_int_distribution<int> uniform(0, ABSL_ARRAYSIZE(words) - 1);  for (int i = 0; i < table_size; i++) {    all_indices.push_back(i);    do {      keys[i].clear();      for (int j = 0; j < WordsPerKey; j++) {        absl::StrAppend(&keys[i], j > 0 ? " " : "", words[uniform(rng)]);      }    } while (!t.insert(keys[i]).second);  }  // Create a list of strings to lookup: a permutation of the array of  // keys we just created, with repeats.  "Hot" keys get repeated more.  std::shuffle(all_indices.begin(), all_indices.end(), rng);  const int num_hot = table_size * kFractionOfKeysThatAreHot;  const int num_cold = table_size - num_hot;  std::vector<int> hot_indices(all_indices.begin(),                               all_indices.begin() + num_hot);  std::vector<int> indices;  for (int i = 0; i < kNumLookupsOfColdKeys; i++) {    indices.insert(indices.end(), all_indices.begin(), all_indices.end());  }  for (int i = 0; i < kNumLookupsOfHotKeys - kNumLookupsOfColdKeys; i++) {    indices.insert(indices.end(), hot_indices.begin(), hot_indices.end());  }  std::shuffle(indices.begin(), indices.end(), rng);  ABSL_RAW_CHECK(      num_cold * kNumLookupsOfColdKeys + num_hot * kNumLookupsOfHotKeys ==          indices.size(),      "");  // After constructing the array we probe it with absl::string_views built from  // test_strings.  This means operator== won't see equal pointers, so  // it'll have to check for equal lengths and equal characters.  std::vector<std::string> test_strings(indices.size());  for (int i = 0; i < indices.size(); i++) {    test_strings[i] = keys[indices[i]];  }  // Run the benchmark. It includes map construction but is mostly  // map lookups.  for (auto _ : state) {    Map h(table_size);    for (int i = 0; i < table_size; i++) {      h[keys[i]] = i * 2;    }    ABSL_RAW_CHECK(h.size() == table_size, "");    uint64_t sum = 0;    for (int i = 0; i < indices.size(); i++) {      sum += h[test_strings[i]];    }    benchmark::DoNotOptimize(sum);  }}void BM_StdMap_4(benchmark::State& state) {  StringViewMapBenchmark<EasyMap, 4>(state);}BENCHMARK(BM_StdMap_4)->Range(1 << 10, 1 << 16);void BM_StdMap_8(benchmark::State& state) {  StringViewMapBenchmark<EasyMap, 8>(state);}BENCHMARK(BM_StdMap_8)->Range(1 << 10, 1 << 16);void BM_CopyToStringNative(benchmark::State& state) {  std::string src(state.range(0), 'x');  absl::string_view sv(src);  std::string dst;  for (auto _ : state) {    dst.assign(sv.begin(), sv.end());  }}BENCHMARK(BM_CopyToStringNative)->Range(1 << 3, 1 << 12);void BM_AppendToStringNative(benchmark::State& state) {  std::string src(state.range(0), 'x');  absl::string_view sv(src);  std::string dst;  for (auto _ : state) {    dst.clear();    dst.insert(dst.end(), sv.begin(), sv.end());  }}BENCHMARK(BM_AppendToStringNative)->Range(1 << 3, 1 << 12);}  // namespace
 |