外观
parallel/benchmark.cpp
配套源码,运行方法见同目录 README。返回实验总览。
cpp
// Original scheduling benchmark: uneven independent jobs, exact comparison.
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
using Clock = std::chrono::steady_clock;
using Values = std::vector<std::uint64_t>;
static std::size_t argument(const char *text, std::size_t maximum) {
std::string value(text);
if (value.empty() || value.find_first_not_of("0123456789") != std::string::npos)
throw std::invalid_argument("arguments must be positive decimal integers");
std::size_t used = 0;
auto n = std::stoull(value, &used);
if (used != value.size() || n == 0 || n > maximum)
throw std::invalid_argument("argument outside supported range");
return static_cast<std::size_t>(n);
}
static std::uint64_t work(std::size_t i, std::size_t n) {
// The first quarter is deliberately heavier: static contiguous chunks
// give one worker much more work even though item counts are equal.
unsigned steps = i < n / 4 ? 512 : 16;
std::uint64_t x = static_cast<std::uint64_t>(i) + UINT64_C(0x9e3779b97f4a7c15);
for (unsigned k = 0; k < steps; ++k) {
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
x *= UINT64_C(2685821657736338717);
}
return x;
}
static void range(Values &output, std::size_t begin, std::size_t end) {
for (std::size_t i = begin; i < end; ++i) output[i] = work(i, output.size());
}
static void execute(Values &output, const std::string &policy,
std::size_t workers, std::size_t grain) {
if (policy == "serial") { range(output, 0, output.size()); return; }
std::atomic<std::size_t> next{0};
std::vector<std::thread> threads;
threads.reserve(workers);
try {
for (std::size_t id = 0; id < workers; ++id) {
threads.emplace_back([&, id] {
if (policy == "static") {
range(output, output.size() * id / workers,
output.size() * (id + 1) / workers);
} else {
for (;;) {
std::size_t begin = next.fetch_add(grain, std::memory_order_relaxed);
if (begin >= output.size()) break;
range(output, begin, std::min(begin + grain, output.size()));
}
}
});
}
} catch (...) {
for (auto &thread : threads) thread.join();
throw;
}
for (auto &thread : threads) thread.join();
}
static std::uint64_t checksum(const Values &values) {
std::uint64_t hash = UINT64_C(1469598103934665603);
for (std::uint64_t value : values) { hash ^= value; hash *= UINT64_C(1099511628211); }
return hash;
}
static void measure(const Values &expected, const std::string &policy,
std::size_t workers, std::size_t grain, std::size_t repeats) {
Values output(expected.size());
execute(output, policy, workers, grain); // One untimed warmup.
if (output != expected) throw std::runtime_error("warmup mismatch: " + policy);
std::vector<double> times;
for (std::size_t trial = 0; trial < repeats; ++trial) {
std::fill(output.begin(), output.end(), 0); // Not timed.
const auto begin = Clock::now();
execute(output, policy, workers, grain); // Includes thread creation/join.
const auto end = Clock::now();
if (output != expected) throw std::runtime_error("result mismatch: " + policy);
times.push_back(std::chrono::duration<double, std::milli>(end - begin).count());
}
std::sort(times.begin(), times.end());
double median = times[times.size()/2];
if (times.size() % 2 == 0) median = (times[times.size()/2-1] + median) / 2;
std::cout << policy << ',' << workers << ',' << grain << ',' << expected.size() << ','
<< repeats << ',' << times.front() << ',' << median << ',' << times.back()
<< ',' << checksum(output) << '\n';
}
int main(int argc, char **argv) {
try {
if (argc > 5) throw std::invalid_argument("usage: benchmark [items [threads [grain [repeats]]]]");
std::size_t count = argc > 1 ? argument(argv[1], 5000000) : 200000;
std::size_t workers = argc > 2 ? argument(argv[2], 128) : 4;
std::size_t grain = argc > 3 ? argument(argv[3], 5000000) : 64;
std::size_t repeats = argc > 4 ? argument(argv[4], 31) : 5;
Values expected(count);
range(expected, 0, count);
std::cerr << "compiler=" << __VERSION__ << " hardware_concurrency_hint="
<< std::thread::hardware_concurrency() << '\n';
std::cout << "policy,threads,grain,items,repeats,min_ms,median_ms,max_ms,checksum\n";
std::cout << std::fixed << std::setprecision(6);
measure(expected, "serial", 1, count, repeats);
measure(expected, "static", workers, grain, repeats);
measure(expected, "dynamic", workers, grain, repeats);
std::cerr << "all output elements matched serial reference\n";
} catch (const std::exception &error) {
std::cerr << error.what() << '\n';
return 1;
}
}