xref: /aosp_15_r20/external/abseil-cpp/absl/strings/string_view_benchmark.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
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 
15 #include "absl/strings/string_view.h"
16 
17 #include <algorithm>
18 #include <cstddef>
19 #include <cstdint>
20 #include <map>
21 #include <random>
22 #include <string>
23 #include <unordered_set>
24 #include <vector>
25 
26 #include "benchmark/benchmark.h"
27 #include "absl/base/attributes.h"
28 #include "absl/base/internal/raw_logging.h"
29 #include "absl/base/macros.h"
30 #include "absl/strings/str_cat.h"
31 
32 namespace {
33 
BM_StringViewFromString(benchmark::State & state)34 void BM_StringViewFromString(benchmark::State& state) {
35   std::string s(state.range(0), 'x');
36   std::string* ps = &s;
37   struct SV {
38     SV() = default;
39     explicit SV(const std::string& s) : sv(s) {}
40     absl::string_view sv;
41   } sv;
42   SV* psv = &sv;
43   benchmark::DoNotOptimize(ps);
44   benchmark::DoNotOptimize(psv);
45   for (auto _ : state) {
46     new (psv) SV(*ps);
47     benchmark::DoNotOptimize(sv);
48   }
49 }
50 BENCHMARK(BM_StringViewFromString)->Arg(12)->Arg(128);
51 
52 // Provide a forcibly out-of-line wrapper for operator== that can be used in
53 // benchmarks to measure the impact of inlining.
54 ABSL_ATTRIBUTE_NOINLINE
NonInlinedEq(absl::string_view a,absl::string_view b)55 bool NonInlinedEq(absl::string_view a, absl::string_view b) { return a == b; }
56 
57 // We use functions that cannot be inlined to perform the comparison loops so
58 // that inlining of the operator== can't optimize away *everything*.
59 ABSL_ATTRIBUTE_NOINLINE
DoEqualityComparisons(benchmark::State & state,absl::string_view a,absl::string_view b)60 void DoEqualityComparisons(benchmark::State& state, absl::string_view a,
61                            absl::string_view b) {
62   for (auto _ : state) {
63     benchmark::DoNotOptimize(a == b);
64   }
65 }
66 
BM_EqualIdentical(benchmark::State & state)67 void BM_EqualIdentical(benchmark::State& state) {
68   std::string x(state.range(0), 'a');
69   DoEqualityComparisons(state, x, x);
70 }
71 BENCHMARK(BM_EqualIdentical)->DenseRange(0, 3)->Range(4, 1 << 10);
72 
BM_EqualSame(benchmark::State & state)73 void BM_EqualSame(benchmark::State& state) {
74   std::string x(state.range(0), 'a');
75   std::string y = x;
76   DoEqualityComparisons(state, x, y);
77 }
78 BENCHMARK(BM_EqualSame)
79     ->DenseRange(0, 10)
80     ->Arg(20)
81     ->Arg(40)
82     ->Arg(70)
83     ->Arg(110)
84     ->Range(160, 4096);
85 
BM_EqualDifferent(benchmark::State & state)86 void BM_EqualDifferent(benchmark::State& state) {
87   const int len = state.range(0);
88   std::string x(len, 'a');
89   std::string y = x;
90   if (len > 0) {
91     y[len - 1] = 'b';
92   }
93   DoEqualityComparisons(state, x, y);
94 }
95 BENCHMARK(BM_EqualDifferent)->DenseRange(0, 3)->Range(4, 1 << 10);
96 
97 // This benchmark is intended to check that important simplifications can be
98 // made with absl::string_view comparisons against constant strings. The idea is
99 // that if constant strings cause redundant components of the comparison, the
100 // compiler should detect and eliminate them. Here we use 8 different strings,
101 // each with the same size. Provided our comparison makes the implementation
102 // inline-able by the compiler, it should fold all of these away into a single
103 // size check once per loop iteration.
104 ABSL_ATTRIBUTE_NOINLINE
DoConstantSizeInlinedEqualityComparisons(benchmark::State & state,absl::string_view a)105 void DoConstantSizeInlinedEqualityComparisons(benchmark::State& state,
106                                               absl::string_view a) {
107   for (auto _ : state) {
108     benchmark::DoNotOptimize(a == "aaa");
109     benchmark::DoNotOptimize(a == "bbb");
110     benchmark::DoNotOptimize(a == "ccc");
111     benchmark::DoNotOptimize(a == "ddd");
112     benchmark::DoNotOptimize(a == "eee");
113     benchmark::DoNotOptimize(a == "fff");
114     benchmark::DoNotOptimize(a == "ggg");
115     benchmark::DoNotOptimize(a == "hhh");
116   }
117 }
BM_EqualConstantSizeInlined(benchmark::State & state)118 void BM_EqualConstantSizeInlined(benchmark::State& state) {
119   std::string x(state.range(0), 'a');
120   DoConstantSizeInlinedEqualityComparisons(state, x);
121 }
122 // We only need to check for size of 3, and <> 3 as this benchmark only has to
123 // do with size differences.
124 BENCHMARK(BM_EqualConstantSizeInlined)->DenseRange(2, 4);
125 
126 // This benchmark exists purely to give context to the above timings: this is
127 // what they would look like if the compiler is completely unable to simplify
128 // between two comparisons when they are comparing against constant strings.
129 ABSL_ATTRIBUTE_NOINLINE
DoConstantSizeNonInlinedEqualityComparisons(benchmark::State & state,absl::string_view a)130 void DoConstantSizeNonInlinedEqualityComparisons(benchmark::State& state,
131                                                  absl::string_view a) {
132   for (auto _ : state) {
133     // Force these out-of-line to compare with the above function.
134     benchmark::DoNotOptimize(NonInlinedEq(a, "aaa"));
135     benchmark::DoNotOptimize(NonInlinedEq(a, "bbb"));
136     benchmark::DoNotOptimize(NonInlinedEq(a, "ccc"));
137     benchmark::DoNotOptimize(NonInlinedEq(a, "ddd"));
138     benchmark::DoNotOptimize(NonInlinedEq(a, "eee"));
139     benchmark::DoNotOptimize(NonInlinedEq(a, "fff"));
140     benchmark::DoNotOptimize(NonInlinedEq(a, "ggg"));
141     benchmark::DoNotOptimize(NonInlinedEq(a, "hhh"));
142   }
143 }
144 
BM_EqualConstantSizeNonInlined(benchmark::State & state)145 void BM_EqualConstantSizeNonInlined(benchmark::State& state) {
146   std::string x(state.range(0), 'a');
147   DoConstantSizeNonInlinedEqualityComparisons(state, x);
148 }
149 // We only need to check for size of 3, and <> 3 as this benchmark only has to
150 // do with size differences.
151 BENCHMARK(BM_EqualConstantSizeNonInlined)->DenseRange(2, 4);
152 
BM_CompareSame(benchmark::State & state)153 void BM_CompareSame(benchmark::State& state) {
154   const int len = state.range(0);
155   std::string x;
156   for (int i = 0; i < len; i++) {
157     x += 'a';
158   }
159   std::string y = x;
160   absl::string_view a = x;
161   absl::string_view b = y;
162 
163   for (auto _ : state) {
164     benchmark::DoNotOptimize(a);
165     benchmark::DoNotOptimize(b);
166     benchmark::DoNotOptimize(a.compare(b));
167   }
168 }
169 BENCHMARK(BM_CompareSame)->DenseRange(0, 3)->Range(4, 1 << 10);
170 
BM_CompareFirstOneLess(benchmark::State & state)171 void BM_CompareFirstOneLess(benchmark::State& state) {
172   const int len = state.range(0);
173   std::string x(len, 'a');
174   std::string y = x;
175   y.back() = 'b';
176   absl::string_view a = x;
177   absl::string_view b = y;
178 
179   for (auto _ : state) {
180     benchmark::DoNotOptimize(a);
181     benchmark::DoNotOptimize(b);
182     benchmark::DoNotOptimize(a.compare(b));
183   }
184 }
185 BENCHMARK(BM_CompareFirstOneLess)->DenseRange(1, 3)->Range(4, 1 << 10);
186 
BM_CompareSecondOneLess(benchmark::State & state)187 void BM_CompareSecondOneLess(benchmark::State& state) {
188   const int len = state.range(0);
189   std::string x(len, 'a');
190   std::string y = x;
191   x.back() = 'b';
192   absl::string_view a = x;
193   absl::string_view b = y;
194 
195   for (auto _ : state) {
196     benchmark::DoNotOptimize(a);
197     benchmark::DoNotOptimize(b);
198     benchmark::DoNotOptimize(a.compare(b));
199   }
200 }
201 BENCHMARK(BM_CompareSecondOneLess)->DenseRange(1, 3)->Range(4, 1 << 10);
202 
BM_find_string_view_len_one(benchmark::State & state)203 void BM_find_string_view_len_one(benchmark::State& state) {
204   std::string haystack(state.range(0), '0');
205   absl::string_view s(haystack);
206   for (auto _ : state) {
207     benchmark::DoNotOptimize(s.find("x"));  // not present; length 1
208   }
209 }
210 BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20);
211 
BM_find_string_view_len_two(benchmark::State & state)212 void BM_find_string_view_len_two(benchmark::State& state) {
213   std::string haystack(state.range(0), '0');
214   absl::string_view s(haystack);
215   for (auto _ : state) {
216     benchmark::DoNotOptimize(s.find("xx"));  // not present; length 2
217   }
218 }
219 BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20);
220 
BM_find_one_char(benchmark::State & state)221 void BM_find_one_char(benchmark::State& state) {
222   std::string haystack(state.range(0), '0');
223   absl::string_view s(haystack);
224   for (auto _ : state) {
225     benchmark::DoNotOptimize(s.find('x'));  // not present
226   }
227 }
228 BENCHMARK(BM_find_one_char)->Range(1, 1 << 20);
229 
BM_rfind_one_char(benchmark::State & state)230 void BM_rfind_one_char(benchmark::State& state) {
231   std::string haystack(state.range(0), '0');
232   absl::string_view s(haystack);
233   for (auto _ : state) {
234     benchmark::DoNotOptimize(s.rfind('x'));  // not present
235   }
236 }
237 BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20);
238 
BM_worst_case_find_first_of(benchmark::State & state,int haystack_len)239 void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) {
240   const int needle_len = state.range(0);
241   std::string needle;
242   for (int i = 0; i < needle_len; ++i) {
243     needle += 'a' + i;
244   }
245   std::string haystack(haystack_len, '0');  // 1000 zeros.
246 
247   absl::string_view s(haystack);
248   for (auto _ : state) {
249     benchmark::DoNotOptimize(s.find_first_of(needle));
250   }
251 }
252 
BM_find_first_of_short(benchmark::State & state)253 void BM_find_first_of_short(benchmark::State& state) {
254   BM_worst_case_find_first_of(state, 10);
255 }
256 
BM_find_first_of_medium(benchmark::State & state)257 void BM_find_first_of_medium(benchmark::State& state) {
258   BM_worst_case_find_first_of(state, 100);
259 }
260 
BM_find_first_of_long(benchmark::State & state)261 void BM_find_first_of_long(benchmark::State& state) {
262   BM_worst_case_find_first_of(state, 1000);
263 }
264 
265 BENCHMARK(BM_find_first_of_short)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
266 BENCHMARK(BM_find_first_of_medium)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
267 BENCHMARK(BM_find_first_of_long)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
268 
269 struct EasyMap : public std::map<absl::string_view, uint64_t> {
EasyMap__anon3b60c6cf0111::EasyMap270   explicit EasyMap(size_t) {}
271 };
272 
273 // This templated benchmark helper function is intended to stress operator== or
274 // operator< in a realistic test.  It surely isn't entirely realistic, but it's
275 // a start.  The test creates a map of type Map, a template arg, and populates
276 // it with table_size key/value pairs. Each key has WordsPerKey words.  After
277 // creating the map, a number of lookups are done in random order.  Some keys
278 // are used much more frequently than others in this phase of the test.
279 template <typename Map, int WordsPerKey>
StringViewMapBenchmark(benchmark::State & state)280 void StringViewMapBenchmark(benchmark::State& state) {
281   const int table_size = state.range(0);
282   const double kFractionOfKeysThatAreHot = 0.2;
283   const int kNumLookupsOfHotKeys = 20;
284   const int kNumLookupsOfColdKeys = 1;
285   const char* words[] = {"the",   "quick",  "brown",    "fox",      "jumped",
286                          "over",  "the",    "lazy",     "dog",      "and",
287                          "found", "a",      "large",    "mushroom", "and",
288                          "a",     "couple", "crickets", "eating",   "pie"};
289   // Create some keys that consist of words in random order.
290   std::random_device r;
291   std::seed_seq seed({r(), r(), r(), r(), r(), r(), r(), r()});
292   std::mt19937 rng(seed);
293   std::vector<std::string> keys(table_size);
294   std::vector<int> all_indices;
295   const int kBlockSize = 1 << 12;
296   std::unordered_set<std::string> t(kBlockSize);
297   std::uniform_int_distribution<int> uniform(0, ABSL_ARRAYSIZE(words) - 1);
298   for (int i = 0; i < table_size; i++) {
299     all_indices.push_back(i);
300     do {
301       keys[i].clear();
302       for (int j = 0; j < WordsPerKey; j++) {
303         absl::StrAppend(&keys[i], j > 0 ? " " : "", words[uniform(rng)]);
304       }
305     } while (!t.insert(keys[i]).second);
306   }
307 
308   // Create a list of strings to lookup: a permutation of the array of
309   // keys we just created, with repeats.  "Hot" keys get repeated more.
310   std::shuffle(all_indices.begin(), all_indices.end(), rng);
311   const int num_hot = table_size * kFractionOfKeysThatAreHot;
312   const int num_cold = table_size - num_hot;
313   std::vector<int> hot_indices(all_indices.begin(),
314                                all_indices.begin() + num_hot);
315   std::vector<int> indices;
316   for (int i = 0; i < kNumLookupsOfColdKeys; i++) {
317     indices.insert(indices.end(), all_indices.begin(), all_indices.end());
318   }
319   for (int i = 0; i < kNumLookupsOfHotKeys - kNumLookupsOfColdKeys; i++) {
320     indices.insert(indices.end(), hot_indices.begin(), hot_indices.end());
321   }
322   std::shuffle(indices.begin(), indices.end(), rng);
323   ABSL_RAW_CHECK(
324       num_cold * kNumLookupsOfColdKeys + num_hot * kNumLookupsOfHotKeys ==
325           indices.size(),
326       "");
327   // After constructing the array we probe it with absl::string_views built from
328   // test_strings.  This means operator== won't see equal pointers, so
329   // it'll have to check for equal lengths and equal characters.
330   std::vector<std::string> test_strings(indices.size());
331   for (int i = 0; i < indices.size(); i++) {
332     test_strings[i] = keys[indices[i]];
333   }
334 
335   // Run the benchmark. It includes map construction but is mostly
336   // map lookups.
337   for (auto _ : state) {
338     Map h(table_size);
339     for (int i = 0; i < table_size; i++) {
340       h[keys[i]] = i * 2;
341     }
342     ABSL_RAW_CHECK(h.size() == table_size, "");
343     uint64_t sum = 0;
344     for (int i = 0; i < indices.size(); i++) {
345       sum += h[test_strings[i]];
346     }
347     benchmark::DoNotOptimize(sum);
348   }
349 }
350 
BM_StdMap_4(benchmark::State & state)351 void BM_StdMap_4(benchmark::State& state) {
352   StringViewMapBenchmark<EasyMap, 4>(state);
353 }
354 BENCHMARK(BM_StdMap_4)->Range(1 << 10, 1 << 16);
355 
BM_StdMap_8(benchmark::State & state)356 void BM_StdMap_8(benchmark::State& state) {
357   StringViewMapBenchmark<EasyMap, 8>(state);
358 }
359 BENCHMARK(BM_StdMap_8)->Range(1 << 10, 1 << 16);
360 
BM_CopyToStringNative(benchmark::State & state)361 void BM_CopyToStringNative(benchmark::State& state) {
362   std::string src(state.range(0), 'x');
363   absl::string_view sv(src);
364   std::string dst;
365   for (auto _ : state) {
366     dst.assign(sv.begin(), sv.end());
367   }
368 }
369 BENCHMARK(BM_CopyToStringNative)->Range(1 << 3, 1 << 12);
370 
BM_AppendToStringNative(benchmark::State & state)371 void BM_AppendToStringNative(benchmark::State& state) {
372   std::string src(state.range(0), 'x');
373   absl::string_view sv(src);
374   std::string dst;
375   for (auto _ : state) {
376     dst.clear();
377     dst.insert(dst.end(), sv.begin(), sv.end());
378   }
379 }
380 BENCHMARK(BM_AppendToStringNative)->Range(1 << 3, 1 << 12);
381 
382 }  // namespace
383