blob: 2d2b8a96091ab187c950f569b67b4cad51522295 [file] [log] [blame]
Sebastian Pop65115202016-12-30 18:01:36 +00001
Samuel Benzaquencfe3d782018-10-30 15:54:22 +00002#include <cstdint>
3#include <new>
4#include <vector>
5
6#include "CartesianBenchmarks.hpp"
Sebastian Pop65115202016-12-30 18:01:36 +00007#include "GenerateInput.hpp"
Samuel Benzaquencfe3d782018-10-30 15:54:22 +00008#include "benchmark/benchmark.h"
9#include "test_macros.h"
Sebastian Pop65115202016-12-30 18:01:36 +000010
11constexpr std::size_t MAX_STRING_LEN = 8 << 14;
12
13// Benchmark when there is no match.
14static void BM_StringFindNoMatch(benchmark::State &state) {
15 std::string s1(state.range(0), '-');
16 std::string s2(8, '*');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000017 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000018 benchmark::DoNotOptimize(s1.find(s2));
19}
20BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN);
21
22// Benchmark when the string matches first time.
23static void BM_StringFindAllMatch(benchmark::State &state) {
24 std::string s1(MAX_STRING_LEN, '-');
25 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000026 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000027 benchmark::DoNotOptimize(s1.find(s2));
28}
29BENCHMARK(BM_StringFindAllMatch)->Range(1, MAX_STRING_LEN);
30
31// Benchmark when the string matches somewhere in the end.
32static void BM_StringFindMatch1(benchmark::State &state) {
33 std::string s1(MAX_STRING_LEN / 2, '*');
34 s1 += std::string(state.range(0), '-');
35 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000036 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000037 benchmark::DoNotOptimize(s1.find(s2));
38}
39BENCHMARK(BM_StringFindMatch1)->Range(1, MAX_STRING_LEN / 4);
40
41// Benchmark when the string matches somewhere from middle to the end.
42static void BM_StringFindMatch2(benchmark::State &state) {
43 std::string s1(MAX_STRING_LEN / 2, '*');
44 s1 += std::string(state.range(0), '-');
45 s1 += std::string(state.range(0), '*');
46 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000047 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000048 benchmark::DoNotOptimize(s1.find(s2));
49}
50BENCHMARK(BM_StringFindMatch2)->Range(1, MAX_STRING_LEN / 4);
51
Eric Fiselier718a8ec2018-07-10 04:11:22 +000052static void BM_StringCtorDefault(benchmark::State &state) {
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000053 for (auto _ : state) {
54 std::string Default;
55 benchmark::DoNotOptimize(Default);
Eric Fiselier718a8ec2018-07-10 04:11:22 +000056 }
57}
58BENCHMARK(BM_StringCtorDefault);
59
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000060enum class Length { Empty, Small, Large, Huge };
61struct AllLengths : EnumValuesAsTuple<AllLengths, Length, 4> {
62 static constexpr const char* Names[] = {"Empty", "Small", "Large", "Huge"};
63};
64
65enum class Opacity { Opaque, Transparent };
66struct AllOpacity : EnumValuesAsTuple<AllOpacity, Opacity, 2> {
67 static constexpr const char* Names[] = {"Opaque", "Transparent"};
68};
69
70enum class DiffType { Control, ChangeFirst, ChangeMiddle, ChangeLast };
71struct AllDiffTypes : EnumValuesAsTuple<AllDiffTypes, DiffType, 4> {
72 static constexpr const char* Names[] = {"Control", "ChangeFirst",
73 "ChangeMiddle", "ChangeLast"};
74};
75
Samuel Benzaquen935e0092019-03-21 16:06:15 +000076static constexpr char kSmallStringLiteral[] = "012345678";
77
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000078TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
79 switch (D) {
80 case DiffType::Control:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000081 return kSmallStringLiteral;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000082 case DiffType::ChangeFirst:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000083 return "-12345678";
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000084 case DiffType::ChangeMiddle:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000085 return "0123-5678";
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000086 case DiffType::ChangeLast:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000087 return "01234567-";
Eric Fiselier718a8ec2018-07-10 04:11:22 +000088 }
89}
Eric Fiselier718a8ec2018-07-10 04:11:22 +000090
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000091TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) {
92#define LARGE_STRING_FIRST "123456789012345678901234567890"
93#define LARGE_STRING_SECOND "234567890123456789012345678901"
94 switch (D) {
95 case DiffType::Control:
96 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
97 case DiffType::ChangeFirst:
98 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
99 case DiffType::ChangeMiddle:
100 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2";
101 case DiffType::ChangeLast:
102 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-";
103 }
104}
105
106TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) {
107#define HUGE_STRING0 "0123456789"
108#define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0
109#define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1
110#define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2
111#define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3
112 switch (D) {
113 case DiffType::Control:
114 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
115 case DiffType::ChangeFirst:
116 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
117 case DiffType::ChangeMiddle:
118 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789";
119 case DiffType::ChangeLast:
120 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-";
121 }
122}
123
124TEST_ALWAYS_INLINE std::string makeString(Length L,
125 DiffType D = DiffType::Control,
126 Opacity O = Opacity::Transparent) {
127 switch (L) {
128 case Length::Empty:
129 return maybeOpaque("", O == Opacity::Opaque);
130 case Length::Small:
131 return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
132 case Length::Large:
133 return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
134 case Length::Huge:
135 return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
136 }
137}
138
139template <class Length, class Opaque>
140struct StringConstructDestroyCStr {
141 static void run(benchmark::State& state) {
142 for (auto _ : state) {
143 benchmark::DoNotOptimize(
144 makeString(Length(), DiffType::Control, Opaque()));
145 }
146 }
147
148 static std::string name() {
149 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
150 }
151};
152
153template <class Length, bool MeasureCopy, bool MeasureDestroy>
154static void StringCopyAndDestroy(benchmark::State& state) {
155 static constexpr size_t NumStrings = 1024;
156 auto Orig = makeString(Length());
157 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
158
159 while (state.KeepRunningBatch(NumStrings)) {
160 if (!MeasureCopy)
161 state.PauseTiming();
162 for (size_t I = 0; I < NumStrings; ++I) {
163 ::new (static_cast<void*>(Storage + I)) std::string(Orig);
164 }
165 if (!MeasureCopy)
166 state.ResumeTiming();
167 if (!MeasureDestroy)
168 state.PauseTiming();
169 for (size_t I = 0; I < NumStrings; ++I) {
170 using S = std::string;
171 reinterpret_cast<S*>(Storage + I)->~S();
172 }
173 if (!MeasureDestroy)
174 state.ResumeTiming();
175 }
176}
177
178template <class Length>
179struct StringCopy {
180 static void run(benchmark::State& state) {
181 StringCopyAndDestroy<Length, true, false>(state);
182 }
183
184 static std::string name() { return "BM_StringCopy" + Length::name(); }
185};
186
187template <class Length>
188struct StringDestroy {
189 static void run(benchmark::State& state) {
190 StringCopyAndDestroy<Length, false, true>(state);
191 }
192
193 static std::string name() { return "BM_StringDestroy" + Length::name(); }
194};
195
196template <class Length>
197struct StringMove {
198 static void run(benchmark::State& state) {
199 // Keep two object locations and move construct back and forth.
Eric Fiselier44966cf2018-11-13 19:16:19 +0000200 std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2];
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000201 using S = std::string;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000202 size_t I = 0;
Eric Fiselier44966cf2018-11-13 19:16:19 +0000203 S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length()));
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000204 for (auto _ : state) {
205 // Switch locations.
206 I ^= 1;
207 benchmark::DoNotOptimize(Storage);
208 // Move construct into the new location,
Eric Fiselier44966cf2018-11-13 19:16:19 +0000209 S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS));
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000210 // then destroy the old one.
Eric Fiselier44966cf2018-11-13 19:16:19 +0000211 newS->~S();
212 newS = tmpS;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000213 }
Eric Fiselier44966cf2018-11-13 19:16:19 +0000214 newS->~S();
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000215 }
216
217 static std::string name() { return "BM_StringMove" + Length::name(); }
218};
219
220enum class Relation { Eq, Less, Compare };
221struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
222 static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
223};
224
225template <class Rel, class LHLength, class RHLength, class DiffType>
226struct StringRelational {
227 static void run(benchmark::State& state) {
228 auto Lhs = makeString(RHLength());
229 auto Rhs = makeString(LHLength(), DiffType());
230 for (auto _ : state) {
231 benchmark::DoNotOptimize(Lhs);
232 benchmark::DoNotOptimize(Rhs);
233 switch (Rel()) {
234 case Relation::Eq:
235 benchmark::DoNotOptimize(Lhs == Rhs);
236 break;
237 case Relation::Less:
238 benchmark::DoNotOptimize(Lhs < Rhs);
239 break;
240 case Relation::Compare:
241 benchmark::DoNotOptimize(Lhs.compare(Rhs));
242 break;
243 }
244 }
245 }
246
247 static bool skip() {
248 // Eq is commutative, so skip half the matrix.
249 if (Rel() == Relation::Eq && LHLength() > RHLength())
250 return true;
251 // We only care about control when the lengths differ.
252 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
253 return true;
254 // For empty, only control matters.
255 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
256 return true;
257 return false;
258 }
259
260 static std::string name() {
261 return "BM_StringRelational" + Rel::name() + LHLength::name() +
262 RHLength::name() + DiffType::name();
263 }
264};
265
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000266template <class Rel, class LHLength, class DiffType>
267struct StringRelationalLiteral {
268 static void run(benchmark::State& state) {
269 auto Lhs = makeString(LHLength(), DiffType());
270 for (auto _ : state) {
271 benchmark::DoNotOptimize(Lhs);
272 switch (Rel()) {
273 case Relation::Eq:
274 benchmark::DoNotOptimize(Lhs == kSmallStringLiteral);
275 break;
276 case Relation::Less:
277 benchmark::DoNotOptimize(Lhs < kSmallStringLiteral);
278 break;
279 case Relation::Compare:
280 benchmark::DoNotOptimize(Lhs.compare(kSmallStringLiteral));
281 break;
282 }
283 }
284 }
285
286 static bool skip() {
287 // Doesn't matter how they differ if they have different size.
288 if (LHLength() != Length::Small && DiffType() != ::DiffType::Control)
289 return true;
290 // We don't need huge. Doensn't give anything different than Large.
291 if (LHLength() == Length::Huge)
292 return true;
293 return false;
294 }
295
296 static std::string name() {
297 return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() +
298 DiffType::name();
299 }
300};
301
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000302enum class Depth { Shallow, Deep };
303struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
304 static constexpr const char* Names[] = {"Shallow", "Deep"};
305};
306
307enum class Temperature { Hot, Cold };
308struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
309 static constexpr const char* Names[] = {"Hot", "Cold"};
310};
311
312template <class Temperature, class Depth, class Length>
313struct StringRead {
314 void run(benchmark::State& state) const {
315 static constexpr size_t NumStrings =
316 Temperature() == ::Temperature::Hot
317 ? 1 << 10
318 : /* Enough strings to overflow the cache */ 1 << 20;
319 static_assert((NumStrings & (NumStrings - 1)) == 0,
320 "NumStrings should be a power of two to reduce overhead.");
321
322 std::vector<std::string> Values(NumStrings, makeString(Length()));
323 size_t I = 0;
324 for (auto _ : state) {
325 // Jump long enough to defeat cache locality, and use a value that is
326 // coprime with NumStrings to ensure we visit every element.
327 I = (I + 17) % NumStrings;
328 const auto& V = Values[I];
329
330 // Read everything first. Escaping data() through DoNotOptimize might
331 // cause the compiler to have to recalculate information about `V` due to
332 // aliasing.
333 const char* const Data = V.data();
334 const size_t Size = V.size();
335 benchmark::DoNotOptimize(Data);
336 benchmark::DoNotOptimize(Size);
337 if (Depth() == ::Depth::Deep) {
338 // Read into the payload. This mainly shows the benefit of SSO when the
339 // data is cold.
340 benchmark::DoNotOptimize(*Data);
341 }
342 }
343 }
344
345 static bool skip() {
346 // Huge does not give us anything that Large doesn't have. Skip it.
347 if (Length() == ::Length::Huge) {
348 return true;
349 }
350 return false;
351 }
352
353 std::string name() const {
354 return "BM_StringRead" + Temperature::name() + Depth::name() +
355 Length::name();
356 }
357};
358
359void sanityCheckGeneratedStrings() {
360 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
361 const auto LhsString = makeString(Lhs);
362 for (auto Rhs :
363 {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
364 if (Lhs > Rhs)
365 continue;
366 const auto RhsString = makeString(Rhs);
367
368 // The smaller one must be a prefix of the larger one.
369 if (RhsString.find(LhsString) != 0) {
370 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n",
371 static_cast<int>(Lhs), static_cast<int>(Rhs));
372 std::abort();
373 }
374 }
375 }
376 // Verify the autogenerated diffs
377 for (auto L : {Length::Small, Length::Large, Length::Huge}) {
378 const auto Control = makeString(L);
379 const auto Verify = [&](std::string Exp, size_t Pos) {
380 // Only change on the Pos char.
381 if (Control[Pos] != Exp[Pos]) {
382 Exp[Pos] = Control[Pos];
383 if (Control == Exp)
384 return;
385 }
386 fprintf(stderr, "Invalid autogenerated diff with size %d\n",
387 static_cast<int>(L));
388 std::abort();
389 };
390 Verify(makeString(L, DiffType::ChangeFirst), 0);
391 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
392 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
393 }
394}
395
396int main(int argc, char** argv) {
397 benchmark::Initialize(&argc, argv);
398 if (benchmark::ReportUnrecognizedArguments(argc, argv))
399 return 1;
400
401 sanityCheckGeneratedStrings();
402
403 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
404 AllOpacity>();
405 makeCartesianProductBenchmark<StringCopy, AllLengths>();
406 makeCartesianProductBenchmark<StringMove, AllLengths>();
407 makeCartesianProductBenchmark<StringDestroy, AllLengths>();
408 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
409 AllLengths, AllDiffTypes>();
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000410 makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations,
411 AllLengths, AllDiffTypes>();
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000412 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
413 AllLengths>();
414 benchmark::RunSpecifiedBenchmarks();
415}