1/**
2 * Copyright (c) Glow Contributors. See CONTRIBUTORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "benchmark/benchmark.h"
18
19#include "glow/Backends/DeviceManager.h"
20#include "glow/Optimizer/GraphOptimizer/GraphOptimizer.h"
21#include "glow/Runtime/Executor/ThreadPoolExecutor.h"
22#include "glow/Runtime/HostManager/HostManager.h"
23
24#include "CPUBackend.h"
25
26#include <future>
27
28using namespace glow;
29using namespace glow::runtime;
30
31//===--------------------------------------------------------------------===//
32// Benchmark Declaration and Instantiation Macros //
33//===--------------------------------------------------------------------===//
34
35/// Declare a subclass of ExecutorBenchmark and override its setUpModule and
36/// setUpDAG methods with the given moduleCreator and dagCreator functions.
37#define DECLARE_EXECUTOR_BENCHMARK(name, moduleCreator, dagCreator) \
38 template <typename BackendTy> \
39 class name##ExecutorBenchmark : public ExecutorBenchmark<BackendTy> { \
40 protected: \
41 void setUpModule(benchmark::State &state) override { \
42 this->mod_ = moduleCreator(); \
43 } \
44 void setUpDAG(benchmark::State &state) override { \
45 this->dag_ = dagCreator(this->deviceManagersFunctions_[0]); \
46 this->dag_->root->module = this->mod_.get(); \
47 } \
48 };
49
50/// Declare a subclass of an arbitrary Benchmark class and override its
51/// setUpModule method with the given moduleCreator function.
52#define DECLARE_RUNTIME_COMPONENT_BENCHMARK(name, moduleCreator, component) \
53 template <typename BackendTy> \
54 class name##component##Benchmark : public component##Benchmark<BackendTy> { \
55 protected: \
56 void setUpModule(benchmark::State &state) override { \
57 this->mod_ = moduleCreator(); \
58 } \
59 };
60
61/// Declare subclasses of all RuntimeBenchmark subclasses with appropriate
62/// overrides.
63#define DECLARE_RUNTIME_BENCHMARK(name, moduleCreator, dagCreator) \
64 DECLARE_RUNTIME_COMPONENT_BENCHMARK(name, moduleCreator, HostManager) \
65 DECLARE_EXECUTOR_BENCHMARK(name, moduleCreator, dagCreator) \
66 DECLARE_RUNTIME_COMPONENT_BENCHMARK(name, moduleCreator, DeviceManager)
67
68/// Define a RuntimeBenchmark subclass declared using
69/// DECLARE_XXX_BENCHMARK for a specific backend and component. This instance
70/// calls RuntimeBenchmark::runBenchmark to run the benchmark.
71#define INSTANTIATE_RUNTIME_COMPONENT_BENCHMARK(name, backend, component) \
72 BENCHMARK_TEMPLATE_DEFINE_F(name##component##Benchmark, component##backend, \
73 backend) \
74 (benchmark::State & state) { runBenchmark(state); } \
75 BENCHMARK_REGISTER_F(name##component##Benchmark, component##backend) \
76 ->Unit(benchmark::kMicrosecond);
77
78/// Define RuntimeBenchmark subclasses for all runtime components.
79#define INSTANTIATE_RUNTIME_BENCHMARK(name, backend) \
80 INSTANTIATE_RUNTIME_COMPONENT_BENCHMARK(name, backend, HostManager) \
81 INSTANTIATE_RUNTIME_COMPONENT_BENCHMARK(name, backend, Executor) \
82 INSTANTIATE_RUNTIME_COMPONENT_BENCHMARK(name, backend, DeviceManager)
83
84//===--------------------------------------------------------------------===//
85// Common Utility Functions //
86//===--------------------------------------------------------------------===//
87
88/// Set up a DeviceManager instance by instantiating it, compiling all the
89/// functions in \p mod using \p backend and adding them to the DeviceManager
90/// instance. A reference to the configured DeviceManager instance is returned
91/// in \p deviceManager, and all CompiledFunctions creating during compilation
92/// are returned in \p deviceManagerFunctions.
93void setUpDeviceManagerCommon(
94 benchmark::State &state, std::unique_ptr<Backend> &backend,
95 std::unique_ptr<Module> &mod, std::unique_ptr<DeviceManager> &deviceManager,
96 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>
97 &deviceManagerFunctions) {
98
99 // Check that the backend is valid.
100 if (!backend) {
101 state.SkipWithError("Unable to set up DeviceManager - backend not set up!");
102 return;
103 }
104
105 // Check that the module is valid.
106 if (!mod) {
107 state.SkipWithError("Unable to set up DeviceManager - module not set up!");
108 return;
109 }
110
111 // Create and initialize the DeviceManager instance.
112 deviceManager =
113 std::unique_ptr<DeviceManager>(DeviceManager::createDeviceManager(
114 DeviceConfig(backend->getBackendName())));
115 bool error = ERR_TO_BOOL(deviceManager->init());
116
117 if (error) {
118 state.SkipWithError("Unable to set up DeviceManager - failed to "
119 "initialize DeviceManager!");
120 return;
121 }
122
123 FunctionMapTy funcs;
124 CompilationContext cctx;
125
126 // Compile all functions in the module.
127 for (auto *function : mod->getFunctions()) {
128 EXIT_ON_ERR(::glow::optimizeFunction(function, *backend, cctx));
129 std::unique_ptr<CompiledFunction> compiledFunction =
130 EXIT_ON_ERR(backend->compile(function));
131 funcs.insert(std::make_pair(function->getName(), compiledFunction.get()));
132 deviceManagerFunctions.insert(
133 std::make_pair(function->getName(), std::move(compiledFunction)));
134 }
135
136 // Add all compiled functions to the DeviceManager instance.
137 std::promise<bool> promise;
138 std::future<bool> future = promise.get_future();
139 deviceManager->addNetwork(mod.get(), funcs,
140 [&promise](const Module * /*mod*/, Error err) {
141 promise.set_value(ERR_TO_BOOL(std::move(err)));
142 });
143 future.wait();
144 error = future.get();
145
146 if (error) {
147 state.SkipWithError(
148 "Unable to set up DeviceManager - failed to add functions!");
149 }
150}
151
152/// Tear down a DeviceManager instance (\p deviceManager) by evicted all
153/// functions added to it and shutting down the device. \p
154/// deviceManagerFunctions contains the names of all resident functions.
155void tearDownDeviceManagerCommon(
156 benchmark::State &state, std::unique_ptr<DeviceManager> &deviceManager,
157 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>
158 &deviceManagerFunctions) {
159 // Check that the DeviceManager is valid.
160 if (!deviceManager) {
161 state.SkipWithError(
162 "Unable to tear down DeviceManager - DeviceManager not set up!");
163 }
164
165 // Evict all functions from the DeviceManager instance.
166 for (const auto &func : deviceManagerFunctions) {
167 std::promise<bool> promise;
168 std::future<bool> future = promise.get_future();
169 deviceManager->evictNetwork(
170 func.first, [&promise](std::string /*name*/, Error err) {
171 promise.set_value(ERR_TO_BOOL(std::move(err)));
172 });
173 future.wait();
174 bool error = future.get();
175
176 if (error) {
177 state.SkipWithError("Unable to tear down DeviceManager - could not "
178 "evict all functions!");
179 }
180 }
181
182 deviceManagerFunctions.clear();
183
184 // Stop the device.
185 bool error = ERR_TO_BOOL(deviceManager->stop());
186 if (error) {
187 state.SkipWithError("Unable to tear down DeviceManager - failed to stop "
188 "DeviceManager!");
189 }
190}
191
192//===--------------------------------------------------------------------===//
193// Benchmark Template Fixture Classes //
194//===--------------------------------------------------------------------===//
195
196/// Abstract base class for all runtime benchmarks. Other than definining the
197/// benchmark interface, this class contains the Backend, Module and
198/// ExecutionContext instances used by all benchmark types.
199template <typename BackendTy>
200class RuntimeBenchmark : public benchmark::Fixture {
201public:
202 /// Set up to run the benchmark.
203 void SetUp(benchmark::State &state) override {
204 setUpBackend(state);
205 setUpModule(state);
206 setUpExecutionContext(state);
207 }
208
209 /// Tear down after the benchmark has run.
210 void TearDown(benchmark::State &state) override {
211 tearDownExecutionContext(state);
212 tearDownModule(state);
213 tearDownBackend(state);
214 }
215
216protected:
217 std::unique_ptr<Backend> &getBackend() { return backend_; }
218 void setUpBackend(benchmark::State &state) {
219 backend_ = glow::make_unique<BackendTy>();
220 }
221 virtual void tearDownBackend(benchmark::State &state) {}
222
223 std::unique_ptr<Module> &getModule() { return mod_; }
224 /// Create the module that will be used for the benchmark.
225 virtual void setUpModule(benchmark::State &state) = 0;
226 virtual void tearDownModule(benchmark::State &state) {}
227
228 std::unique_ptr<ExecutionContext> &getExecutionContext() { return ctx_; }
229 virtual void setUpExecutionContext(benchmark::State &state) {
230 // Check that the module is valid.
231 if (!mod_) {
232 state.SkipWithError(
233 "Unable to set up execution context - module not set up!");
234 return;
235 }
236
237 // Allocate all Placeholders in mod_ and move the bindings into an
238 // ExecutionContext object.
239 auto bindings = glow::make_unique<PlaceholderBindings>();
240 bindings->allocate(mod_->getPlaceholders());
241 ctx_ = glow::make_unique<ExecutionContext>(std::move(bindings));
242 }
243 virtual void tearDownExecutionContext(benchmark::State &state) {}
244
245 virtual void runBenchmark(benchmark::State &state) = 0;
246
247 /// An instance of the Backend the benchmark is running against.
248 std::unique_ptr<Backend> backend_;
249 /// The module to use for the benchmark.
250 std::unique_ptr<Module> mod_;
251 /// The execution context to use for the benchmark.
252 std::unique_ptr<ExecutionContext> ctx_;
253};
254
255/// RuntimeBenchmark subclass that benchmarks at the HostManager level (i.e.
256/// HostManager + Executor + DeviceManager).
257template <typename BackendTy>
258class HostManagerBenchmark : public RuntimeBenchmark<BackendTy> {
259public:
260 void SetUp(benchmark::State &state) override {
261 RuntimeBenchmark<BackendTy>::SetUp(state);
262 setUpHostManager(state);
263 }
264
265 void TearDown(benchmark::State &state) override {
266 RuntimeBenchmark<BackendTy>::TearDown(state);
267 tearDownHostManager(state);
268 }
269
270protected:
271 virtual void setUpHostManager(benchmark::State &state) {
272 // Get references to the backend and module stored in the parent class.
273 // this->xxx() must be used since this is a template class (as is its
274 // superclass).
275 std::unique_ptr<Backend> &backend = this->getBackend();
276 std::unique_ptr<Module> &mod = this->getModule();
277
278 // Check that the backend is valid.
279 if (!backend) {
280 state.SkipWithError(
281 "Unable to set up host manager - backend not set up!");
282 return;
283 }
284
285 // Check that the module is valid.
286 if (!mod) {
287 state.SkipWithError("Unable to set up host manager - module not set up!");
288 return;
289 }
290
291 // Create DeviceConfigs with which to initialize the HostManager
292 // instance.
293 std::vector<std::unique_ptr<DeviceConfig>> configs;
294 for (unsigned i = 0; i < numDeviceManagers_; ++i) {
295 configs.emplace_back(
296 glow::make_unique<DeviceConfig>(backend->getBackendName()));
297 }
298
299 // Create and initialize the HostManager instance.
300 hostManager_ = glow::make_unique<HostManager>(std::move(configs));
301
302 // Remember the names of all functions in the module before passing
303 // ownership to the HostManager.
304 for (auto *function : mod->getFunctions()) {
305 functions_.emplace_back(function->getName());
306 }
307
308 // Add the module to the HostManager instance.
309 CompilationContext cctx;
310 bool error = ERR_TO_BOOL(hostManager_->addNetwork(std::move(mod), cctx));
311 if (error) {
312 state.SkipWithError("Unable to set up host manager - failed to add "
313 "module!");
314 }
315 }
316
317 virtual void tearDownHostManager(benchmark::State &state) {
318 // Check that the HostManager instance is valid.
319 if (!hostManager_) {
320 state.SkipWithError(
321 "Unable to tear down host manager - host manager not set up!");
322 return;
323 }
324
325 // Clear all networks and stop all devices.
326 bool error = ERR_TO_BOOL(hostManager_->clearHost());
327 if (error) {
328 state.SkipWithError(
329 "Unable to tear down host manager - failed to clear host!");
330 }
331
332 functions_.clear();
333 }
334
335 void runBenchmark(benchmark::State &state) override {
336 // Get references to the context stored in the parent class.
337 // this->xxx() must be used since this is a template class (as is its
338 // superclass).
339 std::unique_ptr<ExecutionContext> &ctx = this->getExecutionContext();
340
341 for (auto _ : state) {
342 // Run all functions in the module synchronously.
343 for (const auto &function : functions_) {
344 std::promise<void> promise;
345 std::future<void> future = promise.get_future();
346 hostManager_->runNetwork(
347 function, std::move(ctx),
348 [&promise, &ctx](runtime::RunIdentifierTy /*runId*/, Error err,
349 std::unique_ptr<ExecutionContext> result) {
350 // We don't care about the result but check the error to avoid
351 // uncheck error error.
352 ERR_TO_BOOL(std::move(err));
353 ctx = std::move(result);
354 promise.set_value();
355 });
356 future.wait();
357 }
358 }
359 }
360
361 /// The HostManager instance being benchmarked.
362 std::unique_ptr<HostManager> hostManager_;
363 /// The number of DeviceManagers to use during the benchmark.
364 static constexpr unsigned numDeviceManagers_{1};
365 /// List of functions in the module.
366 std::vector<std::string> functions_;
367};
368
369/// RuntimeBenchmark subclass that benchmarks at the Executor level (i.e.
370/// Executor + DeviceManager).
371template <typename BackendTy>
372class ExecutorBenchmark : public RuntimeBenchmark<BackendTy> {
373public:
374 void SetUp(benchmark::State &state) override {
375 RuntimeBenchmark<BackendTy>::SetUp(state);
376 setUpExecutor(state);
377 }
378
379 void TearDown(benchmark::State &state) override {
380 RuntimeBenchmark<BackendTy>::TearDown(state);
381 tearDownExecutor(state);
382 }
383
384protected:
385 virtual void setUpDeviceManagers(benchmark::State &state) {
386 // Get references to the backend and module stored in the parent class.
387 // this->xxx() must be used since this is a template class (as is its
388 // superclass).
389 std::unique_ptr<Backend> &backend = this->getBackend();
390 std::unique_ptr<Module> &module = this->getModule();
391
392 // Create numDeviceManagers_ DeviceManagers and store references to them as
393 // well as the CompiledFunctions loaded onto them.
394 for (unsigned i = 0; i < numDeviceManagers_; ++i) {
395 std::unique_ptr<DeviceManager> deviceManager;
396 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>
397 deviceManagerFunctions;
398 setUpDeviceManagerCommon(state, backend, module, deviceManager,
399 deviceManagerFunctions);
400 deviceManagers_.insert(std::make_pair(i, std::move(deviceManager)));
401 deviceManagersFunctions_.insert(
402 std::make_pair(i, std::move(deviceManagerFunctions)));
403 }
404 }
405
406 virtual void tearDownDeviceManagers(benchmark::State &state) {
407 // Tear down the numDeviceManagers_ DeviceManagers used for the benchmark.
408 for (unsigned i = 0; i < numDeviceManagers_; ++i) {
409 tearDownDeviceManagerCommon(state, deviceManagers_[i],
410 deviceManagersFunctions_[i]);
411 deviceManagers_.erase(i);
412 deviceManagersFunctions_.erase(i);
413 }
414 }
415
416 virtual void setUpExecutor(benchmark::State &state) {
417 setUpDeviceManagers(state);
418 executor_ = std::unique_ptr<ThreadPoolExecutor>(
419 new ThreadPoolExecutor(deviceManagers_));
420 setUpDAG(state);
421 executor_->createPool(dag_->root.get(), 1, false, false);
422 }
423
424 virtual void tearDownExecutor(benchmark::State &state) {
425 tearDownDeviceManagers(state);
426
427 if (!executor_) {
428 state.SkipWithError(
429 "Unable to tear down executor - executor not set up!");
430 }
431
432 executor_->shutdown();
433 tearDownDAG(state);
434 }
435
436 /// Set up the executor DAG that the Executor taken in as input to
437 /// Executor::run().
438 virtual void setUpDAG(benchmark::State &state) = 0;
439 /// Tear down the executor DAG.
440 virtual void tearDownDAG(benchmark::State &state) {}
441
442 virtual void runBenchmark(benchmark::State &state) override {
443 // Get a reference to the context stored in the parent class.
444 // this->xxx() must be used since this is a template class (as is its
445 // superclass).
446 std::unique_ptr<ExecutionContext> &ctx = this->getExecutionContext();
447 for (auto _ : state) {
448 // Run the DAG synchronously.
449 std::promise<void> promise;
450 std::future<void> future = promise.get_future();
451 executor_->run(
452 (dag_->root).get(), std::move(ctx), /*runId=*/0,
453 [&promise, &ctx](runtime::RunIdentifierTy /*runId*/, Error err,
454 std::unique_ptr<ExecutionContext> result) {
455 // We don't care about the result but check the error to avoid
456 // uncheck error error.
457 ERR_TO_BOOL(std::move(err));
458 ctx = std::move(result);
459 promise.set_value();
460 });
461 future.wait();
462 }
463 }
464
465 /// The Executor instance being benchmarked.
466 std::unique_ptr<Executor> executor_;
467 /// The DAG needed by the Executor.
468 std::unique_ptr<DAG> dag_;
469 /// The number of DeviceManagers to create.
470 static constexpr unsigned numDeviceManagers_{1};
471 /// The DeviceManagers used by the Executor during the benchmark (map from
472 /// DeviceID -> DeviceManager).
473 DeviceManagerMapTy deviceManagers_;
474 /// The CompiledFunctions loaded on the DeviceManagers used by the Executor
475 /// during the benchmark (map from DeviceID -> (map from string ->
476 /// CompiledFunction)).
477 std::unordered_map<
478 DeviceIDTy,
479 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>>
480 deviceManagersFunctions_;
481};
482
483/// RuntimeBenchmark subclass that benchmarks at the DeviceManager level.
484template <typename BackendTy>
485class DeviceManagerBenchmark : public RuntimeBenchmark<BackendTy> {
486public:
487 void SetUp(benchmark::State &state) override {
488 RuntimeBenchmark<BackendTy>::SetUp(state);
489 setUpDeviceManager(state);
490 }
491
492 void TearDown(benchmark::State &state) override {
493 tearDownDeviceManager(state);
494 RuntimeBenchmark<BackendTy>::TearDown(state);
495 }
496
497protected:
498 virtual void setUpDeviceManager(benchmark::State &state) {
499 setUpDeviceManagerCommon(state, this->getBackend(), this->getModule(),
500 deviceManager_, deviceManagerFunctions_);
501 }
502
503 virtual void tearDownDeviceManager(benchmark::State &state) {
504 tearDownDeviceManagerCommon(state, deviceManager_, deviceManagerFunctions_);
505 }
506
507 virtual void runBenchmark(benchmark::State &state) override {
508 // Get a reference to the context stored in the parent class.
509 // this->xxx() must be used since this is a template class (as is its
510 // superclass).
511 std::unique_ptr<ExecutionContext> &ctx = this->getExecutionContext();
512 for (auto _ : state) {
513 // Run all functions added to the DeviceManager.
514 for (const auto &func : deviceManagerFunctions_) {
515 std::promise<void> promise;
516 std::future<void> future = promise.get_future();
517 deviceManager_->runFunction(
518 func.first, std::move(ctx),
519 [&promise, &ctx](runtime::RunIdentifierTy /*runId*/, Error err,
520 std::unique_ptr<ExecutionContext> result) {
521 // We don't care about the result but check the error to avoid
522 // uncheck error error.
523 ERR_TO_BOOL(std::move(err));
524 ctx = std::move(result);
525 promise.set_value();
526 });
527 future.wait();
528 }
529 }
530 }
531
532 /// The DeviceManager instance being benchmarked.
533 std::unique_ptr<DeviceManager> deviceManager_;
534 /// All of the CompiledFunctions added to the DeviceManager (they have to be
535 /// kept somewhere since the DeviceManager class does not own them).
536 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>
537 deviceManagerFunctions_;
538};
539
540//===--------------------------------------------------------------------===//
541// Benchmark Module and DAG Creator Functions //
542//===--------------------------------------------------------------------===//
543
544//----------------------------- Single Node --------------------------------//
545/// Create a module consisting of a single FC operator.
546std::unique_ptr<Module> createSingleNodeModule() {
547 auto mod = glow::make_unique<Module>();
548 auto fn = mod->createFunction("singleNode");
549 PlaceholderBindings bindings;
550
551 auto *input =
552 mod->createPlaceholder(ElemKind::FloatTy, {16, 32}, "input", false);
553 auto *weights =
554 mod->createPlaceholder(ElemKind::FloatTy, {32, 32}, "weights1", false);
555 auto *bias = mod->createPlaceholder(ElemKind::FloatTy, {32}, "bias", false);
556 auto *output =
557 mod->createPlaceholder(ElemKind::FloatTy, {16, 32}, "output", false);
558
559 auto *fc = fn->createFullyConnected("fc", input, weights, bias);
560 fn->createSave("save", fc, output);
561
562 bindings.allocate(weights)->getHandle().clear(0);
563 bindings.allocate(bias)->getHandle().clear(32);
564
565 glow::convertPlaceholdersToConstants(fn, bindings, {input, output});
566
567 return mod;
568}
569
570/// Create an Executor DAG consisting of just one node for the single FC
571/// operator.
572std::unique_ptr<DAG> createSingleNodeDAG(
573 std::unordered_map<std::string, std::unique_ptr<CompiledFunction>>
574 &compiledFunctions) {
575 // The DAG should have one root node and one actual node corresponding to the
576 // CompiledFunction obtained by compiling the singular function in the Module
577 // created by createSingleNodeModule.
578 auto root = glow::make_unique<DAGNode>();
579 auto singleNode = glow::make_unique<DAGNode>();
580
581 root->children.emplace_back(singleNode.get());
582
583 singleNode->parents.emplace_back(root.get());
584 singleNode->deviceRuntimeInfos[0] = DeviceRuntimeInfo();
585 singleNode->name = "singleNode";
586 singleNode->runtimeBundle = glow::make_unique<RuntimeBundle>(
587 compiledFunctions["singleNode"]->getRuntimeBundle());
588
589 std::vector<std::unique_ptr<DAGNode>> nodes;
590 nodes.emplace_back(std::move(singleNode));
591
592 auto dag = glow::make_unique<DAG>();
593 dag->root = std::move(root);
594 dag->nodes = std::move(nodes);
595
596 return dag;
597}
598//--------------------------------------------------------------------------//
599
600//===--------------------------------------------------------------------===//
601// Benchmark Declarations and Instantiations //
602//===--------------------------------------------------------------------===//
603
604// Declare a runtime benchmark named SingleNode that uses createSingleNodeModule
605// to create the module and createSingleNodeDAG to create the Module and DAG for
606// benchmarking. This declares appropriately named subclasses of
607// HostManagerBenchmark, ExecutorBenchmark and DeviceManagerBenchmark.
608DECLARE_RUNTIME_BENCHMARK(SingleNode, createSingleNodeModule,
609 createSingleNodeDAG);
610
611// Instantiate the SingleNode benchmark for the CPU backend. This creates
612// instances of the HostManagerBenchmark, ExecutorBenchmark and
613// DeviceManagerBenchmark subclasses declared by the macro above for the CPU
614// backend.
615INSTANTIATE_RUNTIME_BENCHMARK(SingleNode, CPUBackend);
616
617//===--------------------------------------------------------------------===//
618// Benchmark Main //
619//===--------------------------------------------------------------------===//
620
621// Benchmark main.
622BENCHMARK_MAIN();
623