Skip to content

Commit

Permalink
add a primitive throughput benchmark (#15)
Browse files Browse the repository at this point in the history
* add throughput bench

* add docker bench
  • Loading branch information
geseq authored Jan 22, 2024
1 parent b2c960e commit 14d33ce
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(ENABLE_TESTING "Enable test target generation" OFF)

add_executable (main main.cpp)

set(CPM_USE_LOCAL_PACKAGES ON)
include(cmake/CPM.cmake)
CPMUsePackageLock(package-lock.cmake)

Expand Down Expand Up @@ -60,4 +61,4 @@ if (ENABLE_TESTING)
ENDFOREACH ()
endif()

target_link_libraries(main PRIVATE Boost::intrusive decimal pool)
target_link_libraries(main PRIVATE ${CPP_ORDERBOOK} Boost::intrusive decimal pool)
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM gcc:13 AS cpp-base

RUN set -ex; \
apt-get update && \
apt-get install -y cmake locales && \
locale-gen en_US.UTF-8

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

RUN mkdir /app
COPY . /app
RUN rm -rf /app/.cache
RUN rm -rf /app/build
WORKDIR /app
RUN cmake --preset release
RUN cmake --build --preset release

ENTRYPOINT ["/app/build/release/main"]

CMD []
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '2.2'
services:
cpp-orderbook-bench:
build: .
container_name: cpp-orderbook-bench
cpus: 1
privileged: true # for bench only, to avoid kernel mitigations override
cpuset: "1"
command:
- "-duration"
- "30"
- "-start-after"
- "60"
- "-n"
- "throughput"
79 changes: 78 additions & 1 deletion main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <locale>
#include <random>
#include <sstream>
#include <string>
Expand All @@ -10,6 +12,15 @@
#include "include/orderbook.hpp"
#include "include/types.hpp"

using orderbook::Decimal;
using orderbook::Flag;
using orderbook::MsgType;
using orderbook::OrderBook;
using orderbook::OrderID;
using orderbook::OrderStatus;
using orderbook::Side;
using orderbook::Type;

// Helper function to parse command-line arguments and provide default values.
std::string getCmdOption(char **begin, char **end, const std::string &option, const std::string &default_value = "") {
char **itr = std::find(begin, end, option);
Expand All @@ -21,14 +32,80 @@ std::string getCmdOption(char **begin, char **end, const std::string &option, co

bool cmdOptionExists(char **begin, char **end, const std::string &option) { return std::find(begin, end, option) != end; }

std::tuple<orderbook::Decimal, orderbook::Decimal, orderbook::Decimal, orderbook::Decimal> getInitialVars(const orderbook::Decimal &lowerBound,
const orderbook::Decimal &upperBound,
const orderbook::Decimal &minSpread) {
orderbook::Decimal bid = (lowerBound + upperBound) / orderbook::Decimal(2, 0);
orderbook::Decimal ask = bid - minSpread;
orderbook::Decimal bidQty(10, 0);
orderbook::Decimal askQty(10, 0);

return {bid, ask, bidQty, askQty};
}

std::pair<orderbook::Decimal, orderbook::Decimal> getPrice(orderbook::Decimal bid, orderbook::Decimal ask, const orderbook::Decimal &diff, bool dec) {
if (dec) {
bid = bid - diff;
ask = ask - diff;
} else {
bid = bid + diff;
ask = ask + diff;
}

return {bid, ask};
}

void latency(int64_t seed, int duration, int pd, orderbook::Decimal lowerBound, orderbook::Decimal upperBound, orderbook::Decimal minSpread, bool sched) {
std::cout << "Latency function running...\n";
// Implement the latency benchmark logic.
}

void throughput(int64_t seed, int duration, orderbook::Decimal lowerBound, orderbook::Decimal upperBound, orderbook::Decimal minSpread) {
std::cout << "Throughput function running...\n";
std::cout << "starting throughput benchmark...\n";
// Implement the throughput benchmark logic.
auto n = orderbook::EmptyNotification();
auto ob = std::make_unique<orderbook::OrderBook<orderbook::EmptyNotification>>(n);
orderbook::Decimal bid, ask, bidQty, askQty;
std::tie(bid, ask, bidQty, askQty) = getInitialVars(lowerBound, upperBound, minSpread);

uint64_t tok = 0, buyID = 0, sellID = 0, operations = 0;

auto end = std::chrono::steady_clock::now() + std::chrono::seconds(duration);
std::default_random_engine generator(seed);
std::uniform_int_distribution<int> distribution(0, 9);

auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() < end) {
int r = distribution(generator);
bool decrease = r < 5;

std::tie(bid, ask) = getPrice(bid, ask, minSpread, decrease);
if (bid < lowerBound) {
std::tie(bid, ask) = getPrice(bid, ask, minSpread, false);
} else if (ask > upperBound) {
std::tie(bid, ask) = getPrice(bid, ask, minSpread, true);
}

ob->cancelOrder(++tok, buyID);
ob->cancelOrder(++tok, sellID);
buyID = ++tok;
sellID = ++tok;
ob->addOrder(buyID, buyID, Type::Limit, Side::Buy, bidQty, bid, Decimal(0, 0), Flag::None);
ob->addOrder(sellID, sellID, Type::Limit, Side::Sell, askQty, ask, Decimal(0, 0), Flag::None);

operations += 4; // 2 cancels + 2 adds
}

auto finish = std::chrono::steady_clock::now();
std::chrono::duration<double, std::nano> elapsed = finish - start;
double throughput = operations / (elapsed.count() / 1e9);
double nanosecPerOp = elapsed.count() / operations;

std::cout.imbue(std::locale("en_US.UTF-8"));
std::cout << std::fixed << std::setprecision(2);
std::cout << "Total Ops: " << operations << " ops" << std::endl;
std::cout << "Throughput: " << throughput << " ops/sec" << std::endl;
std::cout << "Avg latency: " << nanosecPerOp << " ns/op" << std::endl;
}

void run(int64_t seed, int duration, int pd, const std::string &lb, const std::string &ub, const std::string &ms, const std::string &n, bool sched) {
Expand Down

0 comments on commit 14d33ce

Please sign in to comment.