Commit 46bf6129 authored by Kenton Varda's avatar Kenton Varda

Factor out benchmark common code.

parent 43c59cc3
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNPROTO_BENCHMARK_BENCHMARK_COMMON_H_
#define CAPNPROTO_BENCHMARK_BENCHMARK_COMMON_H_
#include <unistd.h>
#include <limits>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <semaphore.h>
#include <algorithm>
#include <stdexcept>
#include <stdio.h>
#include <string.h>
namespace capnproto {
namespace benchmark {
static inline uint32_t nextFastRand() {
static constexpr uint32_t A = 1664525;
static constexpr uint32_t C = 1013904223;
static uint32_t state = C;
state = A * state + C;
return state;
}
static inline uint32_t fastRand(uint32_t range) {
return nextFastRand() % range;
}
static inline double fastRandDouble(double range) {
return nextFastRand() * range / std::numeric_limits<uint32_t>::max();
}
inline int32_t div(int32_t a, int32_t b) {
if (b == 0) return std::numeric_limits<int32_t>::max();
// INT_MIN / -1 => SIGFPE. Who knew?
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
return std::numeric_limits<int32_t>::max();
}
return a / b;
}
inline int32_t mod(int32_t a, int32_t b) {
if (b == 0) return std::numeric_limits<int32_t>::max();
// INT_MIN % -1 => SIGFPE. Who knew?
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
return std::numeric_limits<int32_t>::max();
}
return a % b;
}
static const char* const WORDS[] = {
"foo ", "bar ", "baz ", "qux ", "quux ", "corge ", "grault ", "garply ", "waldo ", "fred ",
"plugh ", "xyzzy ", "thud "
};
constexpr size_t WORDS_COUNT = sizeof(WORDS) / sizeof(WORDS[0]);
template <typename T>
class ProducerConsumerQueue {
public:
ProducerConsumerQueue() {
front = new Node;
back = front;
sem_init(&semaphore, 0, 0);
}
~ProducerConsumerQueue() {
while (front != nullptr) {
Node* oldFront = front;
front = front->next;
delete oldFront;
}
sem_destroy(&semaphore);
}
void post(T t) {
back->next = new Node(t);
back = back->next;
sem_post(&semaphore);
}
T next() {
sem_wait(&semaphore);
Node* oldFront = front;
front = front->next;
delete oldFront;
return front->value;
}
private:
struct Node {
T value;
Node* next;
Node(): next(nullptr) {}
Node(T value): value(value), next(nullptr) {}
};
Node* front; // Last node that has been consumed.
Node* back; // Last node in list.
sem_t semaphore;
};
class OsException: public std::exception {
public:
OsException(int error): error(error) {}
~OsException() noexcept {}
const char* what() const noexcept override {
// TODO: Use strerror_r or whatever for thread-safety. Ugh.
return strerror(error);
}
private:
int error;
};
static void writeAll(int fd, const void* buffer, size_t size) {
const char* pos = reinterpret_cast<const char*>(buffer);
while (size > 0) {
ssize_t n = write(fd, pos, size);
if (n <= 0) {
throw OsException(errno);
}
pos += n;
size -= n;
}
}
static void readAll(int fd, void* buffer, size_t size) {
char* pos = reinterpret_cast<char*>(buffer);
while (size > 0) {
ssize_t n = read(fd, pos, size);
if (n <= 0) {
throw OsException(errno);
}
pos += n;
size -= n;
}
}
template <typename BenchmarkMethods, typename Func>
uint64_t passByPipe(Func&& clientFunc, uint64_t iters) {
int clientToServer[2];
int serverToClient[2];
if (pipe(clientToServer) < 0) throw OsException(errno);
if (pipe(serverToClient) < 0) throw OsException(errno);
pid_t child = fork();
if (child == 0) {
// Client.
close(clientToServer[0]);
close(serverToClient[1]);
uint64_t throughput = clientFunc(serverToClient[0], clientToServer[1], iters);
writeAll(clientToServer[1], &throughput, sizeof(throughput));
exit(0);
} else {
// Server.
close(clientToServer[1]);
close(serverToClient[0]);
uint64_t throughput = BenchmarkMethods::server(clientToServer[0], serverToClient[1], iters);
uint64_t clientThroughput = 0;
readAll(clientToServer[0], &clientThroughput, sizeof(clientThroughput));
throughput += clientThroughput;
int status;
if (waitpid(child, &status, 0) != child) {
throw OsException(errno);
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
throw std::logic_error("Child exited abnormally.");
}
return throughput;
}
}
template <typename BenchmarkTypes, typename TestCase, typename Reuse, typename Compression>
uint64_t doBenchmark(const std::string& mode, uint64_t iters) {
typedef typename BenchmarkTypes::template BenchmarkMethods<TestCase, Reuse, Compression>
BenchmarkMethods;
if (mode == "client") {
return BenchmarkMethods::syncClient(STDIN_FILENO, STDOUT_FILENO, iters);
} else if (mode == "server") {
return BenchmarkMethods::server(STDIN_FILENO, STDOUT_FILENO, iters);
} else if (mode == "object") {
return BenchmarkMethods::passByObject(iters, false);
} else if (mode == "object-size") {
return BenchmarkMethods::passByObject(iters, true);
} else if (mode == "bytes") {
return BenchmarkMethods::passByBytes(iters);
} else if (mode == "pipe") {
return passByPipe<BenchmarkMethods>(BenchmarkMethods::syncClient, iters);
} else if (mode == "pipe-async") {
return passByPipe<BenchmarkMethods>(BenchmarkMethods::asyncClient, iters);
} else {
fprintf(stderr, "Unknown mode: %s\n", mode.c_str());
exit(1);
}
}
template <typename BenchmarkTypes, typename TestCase, typename Compression>
uint64_t doBenchmark2(const std::string& mode, const std::string& reuse, uint64_t iters) {
if (reuse == "reuse") {
return doBenchmark<
BenchmarkTypes, TestCase, typename BenchmarkTypes::ReusableResources, Compression>(
mode, iters);
} else if (reuse == "no-reuse") {
return doBenchmark<
BenchmarkTypes, TestCase, typename BenchmarkTypes::SingleUseResources, Compression>(
mode, iters);
} else {
fprintf(stderr, "Unknown reuse mode: %s\n", reuse.c_str());
exit(1);
}
}
template <typename BenchmarkTypes, typename TestCase>
uint64_t doBenchmark3(const std::string& mode, const std::string& reuse,
const std::string& compression, uint64_t iters) {
if (compression == "none") {
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::Uncompressed>(
mode, reuse, iters);
} else if (compression == "snappy") {
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::SnappyCompressed>(
mode, reuse, iters);
} else {
fprintf(stderr, "Unknown compression mode: %s\n", compression.c_str());
exit(1);
}
}
template <typename BenchmarkTypes>
int benchmarkMain(int argc, char* argv[]) {
if (argc != 6) {
fprintf(stderr, "USAGE: %s TEST_CASE MODE REUSE COMPRESSION ITERATION_COUNT\n", argv[0]);
return 1;
}
uint64_t iters = strtoull(argv[5], nullptr, 0);
uint64_t throughput;
std::string testcase = argv[1];
if (testcase == "eval") {
throughput = doBenchmark3<BenchmarkTypes, typename BenchmarkTypes::ExpressionTestCase>(
argv[2], argv[3], argv[4], iters);
} else if (testcase == "catrank") {
throughput = doBenchmark3<BenchmarkTypes, typename BenchmarkTypes::CatRankTestCase>(
argv[2], argv[3], argv[4], iters);
} else if (testcase == "carsales") {
throughput = doBenchmark3<BenchmarkTypes, typename BenchmarkTypes::CarSalesTestCase>(
argv[2], argv[3], argv[4], iters);
} else {
fprintf(stderr, "Unknown test case: %s\n", testcase.c_str());
return 1;
}
fprintf(stdout, "%llu", (long long unsigned int)throughput);
return 0;
}
} // namespace capnproto
} // namespace benchmark
#endif // CAPNPROTO_BENCHMARK_BENCHMARK_COMMON_H_
......@@ -21,17 +21,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <inttypes.h>
#include <iostream>
#include <string>
#include <stddef.h>
#include <limits.h>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <string.h>
#include <limits>
#include "fast-random.h"
#include "benchmark-common.h"
namespace capnproto {
namespace benchmark {
......@@ -85,20 +75,6 @@ struct Expression {
};
};
inline int32_t div(int32_t a, int32_t b) {
if (b == 0) return INT_MAX;
// INT_MIN / -1 => SIGFPE. Who knew?
if (a == INT_MIN && b == -1) return INT_MAX;
return a / b;
}
inline int32_t mod(int32_t a, int32_t b) {
if (b == 0) return INT_MAX;
// INT_MIN % -1 => SIGFPE. Who knew?
if (a == INT_MIN && b == -1) return INT_MAX;
return a % b;
}
int32_t makeExpression(Expression* exp, uint depth) {
exp->op = (Operation)(fastRand(OPERATION_RANGE));
......@@ -202,12 +178,6 @@ public:
// The promotion multiplier is large enough that all the results mentioning "cat" but not "dog"
// should end up at the front ofthe list, which is how we verify the result.
static const char* const WORDS[] = {
"foo ", "bar ", "baz ", "qux ", "quux ", "corge ", "grault ", "garply ", "waldo ", "fred ",
"plugh ", "xyzzy ", "thud "
};
constexpr size_t WORDS_COUNT = sizeof(WORDS) / sizeof(WORDS[0]);
template <typename T>
struct List {
size_t size;
......@@ -533,70 +503,84 @@ struct ReusableObjects {
// =======================================================================================
template <typename TestCase>
uint64_t passByObject(uint64_t iters) {
uint64_t throughput = 0;
for (; iters > 0; --iters) {
arenaPos = arena;
typename TestCase::Request request;
typename TestCase::Expectation expected = TestCase::setupRequest(&request);
typename TestCase::Response response;
TestCase::handleRequest(request, &response);
if (!TestCase::checkResponse(response, expected)) {
throw std::logic_error("Incorrect response.");
}
template <typename TestCase, typename ReuseStrategy, typename Compression>
struct BenchmarkMethods {
static uint64_t syncClient(int inputFd, int outputFd, uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
throughput += (arenaPos - arena) * sizeof(arena[0]);
static uint64_t asyncClientSender(
int outputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
return throughput;
}
static void asyncClientReceiver(
int inputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
template <typename TestCase>
uint64_t doBenchmark(const std::string& mode, uint64_t iters) {
if (mode == "object") {
return passByObject<TestCase>(iters);
} else {
std::cerr << "Unknown mode: " << mode << std::endl;
static uint64_t asyncClient(int inputFd, int outputFd, uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
}
int main(int argc, char* argv[]) {
if (argc != 6) {
std::cerr << "USAGE: " << argv[0]
<< " TEST_CASE MODE REUSE COMPRESSION ITERATION_COUNT" << std::endl;
return 1;
static uint64_t server(int inputFd, int outputFd, uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
uint64_t iters = strtoull(argv[5], nullptr, 0);
static uint64_t passByObject(uint64_t iters, bool countObjectSize) {
uint64_t throughput = 0;
uint64_t throughput;
for (; iters > 0; --iters) {
arenaPos = arena;
std::string testcase = argv[1];
if (testcase == "eval") {
throughput = doBenchmark<ExpressionTestCase>(argv[2], iters);
} else if (testcase == "catrank") {
throughput = doBenchmark<CatRankTestCase>(argv[2], iters);
} else if (testcase == "carsales") {
throughput = doBenchmark<CarSalesTestCase>(argv[2], iters);
} else {
std::cerr << "Unknown test case: " << testcase << std::endl;
return 1;
typename TestCase::Request request;
typename TestCase::Expectation expected = TestCase::setupRequest(&request);
typename TestCase::Response response;
TestCase::handleRequest(request, &response);
if (!TestCase::checkResponse(response, expected)) {
throw std::logic_error("Incorrect response.");
}
throughput += (arenaPos - arena) * sizeof(arena[0]);
}
return throughput;
}
std::cout << throughput << std::endl;
static uint64_t passByBytes(uint64_t iters) {
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
exit(1);
}
};
return 0;
}
struct BenchmarkTypes {
typedef null::ExpressionTestCase ExpressionTestCase;
typedef null::CatRankTestCase CatRankTestCase;
typedef null::CarSalesTestCase CarSalesTestCase;
typedef void SnappyCompressed;
typedef void Uncompressed;
typedef void ReusableResources;
typedef void SingleUseResources;
template <typename TestCase, typename ReuseStrategy, typename Compression>
struct BenchmarkMethods: public null::BenchmarkMethods<TestCase, ReuseStrategy, Compression> {};
};
} // namespace null
} // namespace benchmark
} // namespace capnproto
int main(int argc, char* argv[]) {
return capnproto::benchmark::null::main(argc, argv);
return capnproto::benchmark::benchmarkMain<
capnproto::benchmark::null::BenchmarkTypes>(argc, argv);
}
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNPROTO_BENCHMARK_FAST_RANDOM_H_
#define CAPNPROTO_BENCHMARK_FAST_RANDOM_H_
namespace capnproto {
namespace benchmark {
static inline uint32_t nextFastRand() {
static constexpr uint32_t A = 1664525;
static constexpr uint32_t C = 1013904223;
static uint32_t state = C;
state = A * state + C;
return state;
}
static inline uint32_t fastRand(uint32_t range) {
return nextFastRand() % range;
}
static inline double fastRandDouble(double range) {
return nextFastRand() * range / std::numeric_limits<uint32_t>::max();
}
} // namespace capnproto
} // namespace benchmark
#endif // CAPNPROTO_BENCHMARK_FAST_RANDOM_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment