Coverage Report

Created: 2018-09-25 14:53

/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