/src/mozilla-central/xpcom/rust/gtest/bench-collections/Bench.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | // Overview |
6 | | // -------- |
7 | | // This file measures the speed of various implementations of C++ and Rust |
8 | | // collections (hash tables, etc.) used within the codebase. There are a small |
9 | | // number of benchmarks for each collection type; each benchmark tests certain |
10 | | // operations (insertion, lookup, iteration, etc.) More benchmarks could easily |
11 | | // be envisioned, but this small number is enough to characterize the major |
12 | | // differences between implementations, while keeping the file size and |
13 | | // complexity low. |
14 | | // |
15 | | // Details |
16 | | // ------- |
17 | | // The file uses `MOZ_GTEST_BENCH_F` so that results are integrated into |
18 | | // PerfHerder. It is also designed so that individual test benchmarks can be |
19 | | // run under a profiler. |
20 | | // |
21 | | // The C++ code uses `MOZ_RELEASE_ASSERT` extensively to check values and |
22 | | // ensure operations aren't optimized away by the compiler. The Rust code uses |
23 | | // `assert!()`. These should be roughly equivalent, but aren't guaranteed to be |
24 | | // the same. As a result, the intra-C++ comparisons should be reliable, and the |
25 | | // intra-Rust comparisons should be reliable, but the C++ vs. Rust comparisons |
26 | | // may be less reliable. |
27 | | // |
28 | | // Note that the Rust implementations run very slowly without --enable-release. |
29 | | // |
30 | | // Profiling |
31 | | // --------- |
32 | | // If you want to measure a particular implementation under a profiler such as |
33 | | // Callgrind, do something like this: |
34 | | // |
35 | | // MOZ_RUN_GTEST=1 GTEST_FILTER='*BenchCollections*$IMPL*' |
36 | | // valgrind --tool=callgrind --callgrind-out-file=clgout |
37 | | // $OBJDIR/dist/bin/firefox -unittest |
38 | | // callgrind_annotate --auto=yes clgout > clgann |
39 | | // |
40 | | // where $IMPL is part of an implementation name in a test (e.g. "PLDHash", |
41 | | // "MozHash") and $OBJDIR is an objdir containing a --enable-release build. |
42 | | // |
43 | | // Note that multiple processes are spawned, so `clgout` gets overwritten |
44 | | // multiple times, but the last process to write its profiling data to file is |
45 | | // the one of interest. (Alternatively, use --callgrind-out-file=clgout.%p to |
46 | | // get separate output files for each process, with a PID suffix.) |
47 | | |
48 | | #include "gtest/gtest.h" |
49 | | #include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH |
50 | | #include "mozilla/AllocPolicy.h" |
51 | | #include "mozilla/HashFunctions.h" |
52 | | #include "mozilla/HashTable.h" |
53 | | #include "mozilla/StaticMutex.h" |
54 | | #include "mozilla/TimeStamp.h" |
55 | | #include "PLDHashTable.h" |
56 | | #include <unordered_set> |
57 | | |
58 | | using namespace mozilla; |
59 | | |
60 | | // This function gives a pseudo-random sequence with the following properties: |
61 | | // - Deterministic and platform-independent. |
62 | | // - No duplicates in the first VALS_LEN results, which is useful for ensuring |
63 | | // the tables get to a particular size, and also for guaranteeing lookups |
64 | | // that fail. |
65 | | uintptr_t |
66 | | MyRand() |
67 | 0 | { |
68 | 0 | static uintptr_t s = 0; |
69 | 0 | s = s * 1103515245 + 12345; |
70 | 0 | return s; |
71 | 0 | } |
72 | | |
73 | | // Keep this in sync with Params in bench.rs. |
74 | | struct Params |
75 | | { |
76 | | const char* mConfigName; |
77 | | size_t mNumInserts; // Insert this many unique keys |
78 | | size_t mNumSuccessfulLookups; // Does mNumInserts lookups each time |
79 | | size_t mNumFailingLookups; // Does mNumInserts lookups each time |
80 | | size_t mNumIterations; // Iterates the full table each time |
81 | | bool mRemoveInserts; // Remove all entries at end? |
82 | | }; |
83 | | |
84 | | // We don't use std::unordered_{set,map}, but it's an interesting thing to |
85 | | // benchmark against. |
86 | | // |
87 | | // Keep this in sync with all the other Bench_*() functions. |
88 | | void |
89 | | Bench_Cpp_unordered_set(const Params* aParams, void** aVals, size_t aLen) |
90 | 0 | { |
91 | 0 | std::unordered_set<void*> hs; |
92 | 0 |
|
93 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
94 | 0 | hs.insert(aVals[j]); |
95 | 0 | } |
96 | 0 |
|
97 | 0 | for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { |
98 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
99 | 0 | MOZ_RELEASE_ASSERT(hs.find(aVals[j]) != hs.end()); |
100 | 0 | } |
101 | 0 | } |
102 | 0 |
|
103 | 0 | for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { |
104 | 0 | for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts*2; j++) { |
105 | 0 | MOZ_RELEASE_ASSERT(hs.find(aVals[j]) == hs.end()); |
106 | 0 | } |
107 | 0 | } |
108 | 0 |
|
109 | 0 | for (size_t i = 0; i < aParams->mNumIterations; i++) { |
110 | 0 | size_t n = 0; |
111 | 0 | for (const auto& elem : hs) { |
112 | 0 | (void)elem; |
113 | 0 | n++; |
114 | 0 | } |
115 | 0 | MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); |
116 | 0 | MOZ_RELEASE_ASSERT(hs.size() == n); |
117 | 0 | } |
118 | 0 |
|
119 | 0 | if (aParams->mRemoveInserts) { |
120 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
121 | 0 | MOZ_RELEASE_ASSERT(hs.erase(aVals[j]) == 1); |
122 | 0 | } |
123 | 0 | MOZ_RELEASE_ASSERT(hs.size() == 0); |
124 | 0 | } else { |
125 | 0 | MOZ_RELEASE_ASSERT(hs.size() == aParams->mNumInserts); |
126 | 0 | } |
127 | 0 | } |
128 | | |
129 | | // Keep this in sync with all the other Bench_*() functions. |
130 | | void |
131 | | Bench_Cpp_PLDHashTable(const Params* aParams, void** aVals, size_t aLen) |
132 | 0 | { |
133 | 0 | PLDHashTable hs(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); |
134 | 0 |
|
135 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
136 | 0 | auto entry = static_cast<PLDHashEntryStub*>(hs.Add(aVals[j])); |
137 | 0 | MOZ_RELEASE_ASSERT(!entry->key); |
138 | 0 | entry->key = aVals[j]; |
139 | 0 | } |
140 | 0 |
|
141 | 0 | for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { |
142 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
143 | 0 | MOZ_RELEASE_ASSERT(hs.Search(aVals[j])); |
144 | 0 | } |
145 | 0 | } |
146 | 0 |
|
147 | 0 | for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { |
148 | 0 | for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts*2; j++) { |
149 | 0 | MOZ_RELEASE_ASSERT(!hs.Search(aVals[j])); |
150 | 0 | } |
151 | 0 | } |
152 | 0 |
|
153 | 0 | for (size_t i = 0; i < aParams->mNumIterations; i++) { |
154 | 0 | size_t n = 0; |
155 | 0 | for (auto iter = hs.Iter(); !iter.Done(); iter.Next()) { |
156 | 0 | n++; |
157 | 0 | } |
158 | 0 | MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); |
159 | 0 | MOZ_RELEASE_ASSERT(hs.EntryCount() == n); |
160 | 0 | } |
161 | 0 |
|
162 | 0 | if (aParams->mRemoveInserts) { |
163 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
164 | 0 | hs.Remove(aVals[j]); |
165 | 0 | } |
166 | 0 | MOZ_RELEASE_ASSERT(hs.EntryCount() == 0); |
167 | 0 | } else { |
168 | 0 | MOZ_RELEASE_ASSERT(hs.EntryCount() == aParams->mNumInserts); |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | // Keep this in sync with all the other Bench_*() functions. |
173 | | void |
174 | | Bench_Cpp_MozHashSet(const Params* aParams, void** aVals, size_t aLen) |
175 | 0 | { |
176 | 0 | mozilla::HashSet<void*, mozilla::DefaultHasher<void*>, MallocAllocPolicy> hs; |
177 | 0 |
|
178 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
179 | 0 | MOZ_RELEASE_ASSERT(hs.put(aVals[j])); |
180 | 0 | } |
181 | 0 |
|
182 | 0 | for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { |
183 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
184 | 0 | MOZ_RELEASE_ASSERT(hs.has(aVals[j])); |
185 | 0 | } |
186 | 0 | } |
187 | 0 |
|
188 | 0 | for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { |
189 | 0 | for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts*2; j++) { |
190 | 0 | MOZ_RELEASE_ASSERT(!hs.has(aVals[j])); |
191 | 0 | } |
192 | 0 | } |
193 | 0 |
|
194 | 0 | for (size_t i = 0; i < aParams->mNumIterations; i++) { |
195 | 0 | size_t n = 0; |
196 | 0 | for (auto iter = hs.iter(); !iter.done(); iter.next()) { |
197 | 0 | n++; |
198 | 0 | } |
199 | 0 | MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); |
200 | 0 | MOZ_RELEASE_ASSERT(hs.count() == n); |
201 | 0 | } |
202 | 0 |
|
203 | 0 | if (aParams->mRemoveInserts) { |
204 | 0 | for (size_t j = 0; j < aParams->mNumInserts; j++) { |
205 | 0 | hs.remove(aVals[j]); |
206 | 0 | } |
207 | 0 | MOZ_RELEASE_ASSERT(hs.count() == 0); |
208 | 0 | } else { |
209 | 0 | MOZ_RELEASE_ASSERT(hs.count() == aParams->mNumInserts); |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | extern "C" { |
214 | | void Bench_Rust_HashSet(const Params* params, void** aVals, size_t aLen); |
215 | | void Bench_Rust_FnvHashSet(const Params* params, void** aVals, size_t aLen); |
216 | | void Bench_Rust_FxHashSet(const Params* params, void** aVals, size_t aLen); |
217 | | } |
218 | | |
219 | | static const size_t VALS_LEN = 131072; |
220 | | |
221 | | // Each benchmark measures a different aspect of performance. |
222 | | // Note that no "Inserts" value can exceed VALS_LEN. |
223 | | // Also, if any failing lookups are done, Inserts must be <= VALS_LEN/2. |
224 | | const Params gParamsList[] = { |
225 | | // Successful Failing Remove |
226 | | // Inserts lookups lookups Iterations inserts |
227 | | { "succ_lookups", 1024, 5000, 0, 0, false }, |
228 | | { "fail_lookups", 1024, 0, 5000, 0, false }, |
229 | | { "insert_remove", VALS_LEN, 0, 0, 0, true }, |
230 | | { "iterate", 1024, 0, 0, 5000, false }, |
231 | | }; |
232 | | |
233 | | class BenchCollections : public ::testing::Test |
234 | | { |
235 | | protected: |
236 | | void SetUp() override |
237 | 0 | { |
238 | 0 | StaticMutexAutoLock lock(sValsMutex); |
239 | 0 |
|
240 | 0 | if (!sVals) { |
241 | 0 | sVals = (void**)malloc(VALS_LEN * sizeof(void*)); |
242 | 0 | for (size_t i = 0; i < VALS_LEN; i++) { |
243 | 0 | // This leaves the high 32 bits zero on 64-bit platforms, but that |
244 | 0 | // should still be enough randomness to get typical behaviour. |
245 | 0 | sVals[i] = reinterpret_cast<void*>(uintptr_t(MyRand())); |
246 | 0 | } |
247 | 0 | } |
248 | 0 |
|
249 | 0 | printf("\n"); |
250 | 0 | for (size_t i = 0; i < ArrayLength(gParamsList); i++) { |
251 | 0 | const Params* params = &gParamsList[i]; |
252 | 0 | printf("%14s", params->mConfigName); |
253 | 0 | } |
254 | 0 | printf("%14s\n", "total"); |
255 | 0 | } |
256 | | |
257 | | public: |
258 | | void BenchImpl(void(*aBench)(const Params*, void**, size_t)) |
259 | 0 | { |
260 | 0 | StaticMutexAutoLock lock(sValsMutex); |
261 | 0 |
|
262 | 0 | double total = 0; |
263 | 0 | for (size_t i = 0; i < ArrayLength(gParamsList); i++) { |
264 | 0 | const Params* params = &gParamsList[i]; |
265 | 0 | TimeStamp t1 = TimeStamp::Now(); |
266 | 0 | aBench(params, sVals, VALS_LEN); |
267 | 0 | TimeStamp t2 = TimeStamp::Now(); |
268 | 0 | double t = (t2 - t1).ToMilliseconds(); |
269 | 0 | printf("%11.1f ms", t); |
270 | 0 | total += t; |
271 | 0 | } |
272 | 0 | printf("%11.1f ms\n", total); |
273 | 0 | } |
274 | | |
275 | | private: |
276 | | // Random values used in the benchmarks. |
277 | | static void** sVals; |
278 | | |
279 | | // A mutex that protects all benchmark operations, ensuring that two |
280 | | // benchmarks never run concurrently. |
281 | | static StaticMutex sValsMutex; |
282 | | }; |
283 | | |
284 | | void** BenchCollections::sVals; |
285 | | StaticMutex BenchCollections::sValsMutex; |
286 | | |
287 | | MOZ_GTEST_BENCH_F(BenchCollections, unordered_set, [this] { |
288 | | BenchImpl(Bench_Cpp_unordered_set); |
289 | | }); |
290 | | |
291 | | MOZ_GTEST_BENCH_F(BenchCollections, PLDHash, [this] { |
292 | | BenchImpl(Bench_Cpp_PLDHashTable); |
293 | | }); |
294 | | |
295 | | MOZ_GTEST_BENCH_F(BenchCollections, MozHash, [this] { |
296 | | BenchImpl(Bench_Cpp_MozHashSet); |
297 | | }); |
298 | | |
299 | | MOZ_GTEST_BENCH_F(BenchCollections, RustHash, [this] { |
300 | | BenchImpl(Bench_Rust_HashSet); |
301 | | }); |
302 | | |
303 | | MOZ_GTEST_BENCH_F(BenchCollections, RustFnvHash, [this] { |
304 | | BenchImpl(Bench_Rust_FnvHashSet); |
305 | | }); |
306 | | |
307 | | MOZ_GTEST_BENCH_F(BenchCollections, RustFxHash, [this] { |
308 | | BenchImpl(Bench_Rust_FxHashSet); |
309 | | }); |
310 | | |