|  | // 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_NOINLINE | 
|  | bool 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_NOINLINE | 
|  | void 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_NOINLINE | 
|  | void 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_NOINLINE | 
|  | void 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 | 
|  |  | 
|  | BENCHMARK_MAIN(); |