167 lines
4.5 KiB
C++
167 lines
4.5 KiB
C++
// Copyright 2020 The Marl Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// This file contains a number of benchmarks that do not use marl.
|
|
// They exist to compare marl's performance against other simple scheduler
|
|
// approaches.
|
|
|
|
#include "marl_bench.h"
|
|
|
|
#include "benchmark/benchmark.h"
|
|
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
// Event provides a basic wait-and-signal synchronization primitive.
|
|
class Event {
|
|
public:
|
|
// wait blocks until the event is fired.
|
|
void wait() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
cv_.wait(lock, [&] { return signalled_; });
|
|
}
|
|
|
|
// signal signals the Event, unblocking any calls to wait.
|
|
void signal() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
signalled_ = true;
|
|
cv_.notify_all();
|
|
}
|
|
|
|
private:
|
|
std::condition_variable cv_;
|
|
std::mutex mutex_;
|
|
bool signalled_ = false;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
// A simple multi-thread, single-queue task executor that shares a single mutex
|
|
// across N threads. This implementation suffers from lock contention.
|
|
static void SingleQueueTaskExecutor(benchmark::State& state) {
|
|
using Task = std::function<uint32_t(uint32_t)>;
|
|
|
|
auto const numTasks = Schedule::numTasks(state);
|
|
auto const numThreads = Schedule::numThreads(state);
|
|
|
|
for (auto _ : state) {
|
|
state.PauseTiming();
|
|
|
|
std::mutex mutex;
|
|
// Set everything up with the mutex locked to prevent the threads from
|
|
// performing work while the timing is paused.
|
|
mutex.lock();
|
|
|
|
// Set up the tasks.
|
|
std::queue<Task> tasks;
|
|
for (int i = 0; i < numTasks; i++) {
|
|
tasks.push(Schedule::doSomeWork);
|
|
}
|
|
|
|
auto taskRunner = [&] {
|
|
while (true) {
|
|
Task task;
|
|
|
|
// Take the next task.
|
|
// Note that this lock is likely to block while waiting for other
|
|
// threads.
|
|
mutex.lock();
|
|
if (tasks.size() > 0) {
|
|
task = tasks.front();
|
|
tasks.pop();
|
|
}
|
|
mutex.unlock();
|
|
|
|
if (task) {
|
|
task(123);
|
|
} else {
|
|
return; // done.
|
|
}
|
|
}
|
|
};
|
|
|
|
// Set up the threads.
|
|
std::vector<std::thread> threads;
|
|
for (int i = 0; i < numThreads; i++) {
|
|
threads.emplace_back(std::thread(taskRunner));
|
|
}
|
|
|
|
state.ResumeTiming();
|
|
mutex.unlock(); // Go threads, go!
|
|
|
|
if (numThreads > 0) {
|
|
// Wait for all threads to finish.
|
|
for (auto& thread : threads) {
|
|
thread.join();
|
|
}
|
|
} else {
|
|
// Single-threaded test - just run the worker.
|
|
taskRunner();
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(SingleQueueTaskExecutor)->Apply(Schedule::args);
|
|
|
|
// A simple multi-thread, multi-queue task executor that avoids lock contention.
|
|
// Tasks queues are evenly balanced, and each should take an equal amount of
|
|
// time to execute.
|
|
static void MultiQueueTaskExecutor(benchmark::State& state) {
|
|
using Task = std::function<uint32_t(uint32_t)>;
|
|
using TaskQueue = std::vector<Task>;
|
|
|
|
auto const numTasks = Schedule::numTasks(state);
|
|
auto const numThreads = Schedule::numThreads(state);
|
|
auto const numQueues = std::max(numThreads, 1);
|
|
|
|
// Set up the tasks queues.
|
|
std::vector<TaskQueue> taskQueues(numQueues);
|
|
for (int i = 0; i < numTasks; i++) {
|
|
taskQueues[i % numQueues].emplace_back(Schedule::doSomeWork);
|
|
}
|
|
|
|
for (auto _ : state) {
|
|
if (numThreads > 0) {
|
|
state.PauseTiming();
|
|
Event start;
|
|
|
|
// Set up the threads.
|
|
std::vector<std::thread> threads;
|
|
for (int i = 0; i < numThreads; i++) {
|
|
threads.emplace_back(std::thread([&, i] {
|
|
start.wait();
|
|
for (auto& task : taskQueues[i]) {
|
|
task(123);
|
|
}
|
|
}));
|
|
}
|
|
|
|
state.ResumeTiming();
|
|
start.signal();
|
|
|
|
// Wait for all threads to finish.
|
|
for (auto& thread : threads) {
|
|
thread.join();
|
|
}
|
|
} else {
|
|
// Single-threaded test - just run the tasks.
|
|
for (auto& task : taskQueues[0]) {
|
|
task(123);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(MultiQueueTaskExecutor)->Apply(Schedule::args); |