Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,18 @@ if (ENABLE_TESTING)
endif()

target_link_libraries(main PRIVATE ${CPP_ORDERBOOK} Boost::intrusive decimal pool)

# --- Link-time optimization (LTO/IPO) for optimized builds only. ------------
# Enabled only for Release-type configs and only when the toolchain supports
# it. Debug builds and the sanitizer builds (ASan/UBSan/TSan) are left
# untouched on purpose: LTO is slow under sanitizers and can misbehave. Note
# the sanitizer CI builds the engine via bench/build.sh, not this CMake, so it
# never reaches here; the gate below additionally keeps a Debug configure clean.
include(CheckIPOSupported)
check_ipo_supported(RESULT _ipo_ok OUTPUT _ipo_msg LANGUAGES CXX)
if (_ipo_ok AND CMAKE_BUILD_TYPE MATCHES "^(Release|RelWithDebInfo|MinSizeRel)$")
set_property(TARGET ${CPP_ORDERBOOK} main PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
message(STATUS "LTO/IPO enabled for ${CMAKE_BUILD_TYPE} build")
elseif (NOT _ipo_ok)
message(STATUS "LTO/IPO not supported by toolchain, continuing without it: ${_ipo_msg}")
endif()
12 changes: 11 additions & 1 deletion bench/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,19 @@ for p in "$DECIMAL_INC" "$POOL_INC"; do
fi
done

# --- Link-time optimization. This adapter is a perf/Release artifact, so it
# gets -flto for cross-TU inlining of the matching chain. The single g++ call
# below both compiles and links, so one -flto covers both phases. We DROP -flto
# when ME_EXTRA_CXXFLAGS asks for a sanitizer build (ASan/UBSan/TSan), where LTO
# is slow and can misbehave with the instrumentation.
LTO_FLAG="-flto"
case "${ME_EXTRA_CXXFLAGS:-}" in
*sanitize*) LTO_FLAG="" ;;
esac

# --- Compile the adapter + engine TUs into the .so. -------------------------
OUT="$DIR/cpp_orderbook_adapter.so"
g++ -std=c++20 -O3 -march=native -fPIC -shared \
g++ -std=c++20 -O3 -march=native $LTO_FLAG -fPIC -shared \
-I"$BENCH/api" \
-I"$ENGINE/include" \
-I"$ENGINE/src" \
Expand Down
31 changes: 31 additions & 0 deletions include/function_ref.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <type_traits>
#include <utility>

namespace orderbook {

// Non-owning view over any callable, modeled after std::function_ref (P0792).
// Stores a type-erased object pointer plus a thunk; trivially copyable, two
// pointers wide, performs no allocation. The referenced callable must outlive
// every invocation through the function_ref.
template <class Signature>
class function_ref;

template <class R, class... Args>
class function_ref<R(Args...)> {
void* obj_ = nullptr;
R (*thunk_)(void*, Args...) = nullptr;

public:
template <class F, class = std::enable_if_t<!std::is_same_v<std::decay_t<F>, function_ref>>>
function_ref(F&& f) noexcept
: obj_(const_cast<void*>(static_cast<const void*>(std::addressof(f)))),
thunk_([](void* obj, Args... args) -> R {
return (*static_cast<std::remove_reference_t<F>*>(obj))(std::forward<Args>(args)...);
}) {}

R operator()(Args... args) const { return thunk_(obj_, std::forward<Args>(args)...); }
};

} // namespace orderbook
6 changes: 3 additions & 3 deletions include/orderqueue.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#pragma once

#include <cstdint>
#include <functional>

#include "boost/intrusive/list.hpp"
#include "boost/intrusive/set_hook.hpp"
#include "function_ref.hpp"
#include "order.hpp"

namespace orderbook {

using TradeNotification =
std::function<void(OrderID mOrderID, OrderID tOrderID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price)>;
using PostOrderFill = std::function<void(OrderID canceledOrderID)>;
function_ref<void(OrderID mOrderID, OrderID tOrderID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price)>;
using PostOrderFill = function_ref<void(OrderID canceledOrderID)>;

using OrderList = boost::intrusive::list<Order>;

Expand Down
2 changes: 1 addition & 1 deletion src/pricelevel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void PriceLevel<P>::remove(Order* order) {
}

if (q->len() == 0) {
price_tree_.erase(q->price());
price_tree_.erase(price_tree_.iterator_to(*q));
--depth_;
queue_pool_.release(&*q);
}
Expand Down
16 changes: 8 additions & 8 deletions test/orderqueue_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ TEST_F(OrderQueueTest, TestOrderQueue_ProcessUpdatesTotalQtyOnFullFill) {
oq->append(&o1);
oq->append(&o2);

const TradeNotification tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
const auto tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
Decimal matchedPrice) {};
const PostOrderFill pf = [&oq, &o1, &o2](OrderID id) {
const auto pf = [&oq, &o1, &o2](OrderID id) {
if (id == 1) {
oq->remove(&o1);
} else if (id == 2) {
Expand Down Expand Up @@ -84,9 +84,9 @@ TEST_F(OrderQueueTest, TestOrderQueue_ProcessUpdatesTotalQtyOnExactFill) {
oq->append(&o1);
oq->append(&o2);

const TradeNotification tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
const auto tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
Decimal matchedPrice) {};
const PostOrderFill pf = [&oq, &o1, &o2](OrderID id) {
const auto pf = [&oq, &o1, &o2](OrderID id) {
if (id == 1) {
oq->remove(&o1);
} else if (id == 2) {
Expand All @@ -112,9 +112,9 @@ TEST_F(OrderQueueTest, TestOrderQueue_ProcessZeroesFilledOrderBeforePostFill) {
oq->append(&o2);

bool sawZeroQtyOnPostFill = false;
const TradeNotification tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
const auto tn = [](OrderID makerOrderID, OrderID takerOrderID, OrderStatus makerOrderStatus, OrderStatus takerOrderStatus, Decimal matchedQty,
Decimal matchedPrice) {};
const PostOrderFill pf = [&oq, &o1, &o2, &sawZeroQtyOnPostFill](OrderID id) {
const auto pf = [&oq, &o1, &o2, &sawZeroQtyOnPostFill](OrderID id) {
if (id == 1) {
sawZeroQtyOnPostFill = (o1.qty == Decimal(0, 0));
oq->remove(&o1);
Expand Down Expand Up @@ -146,12 +146,12 @@ TEST_F(OrderQueueTest, TestOrderQueue_ProcessUsesSnapshotForTradeNotificationAft

OrderID makerOrderID = 0;
Decimal matchedPrice(0, 0);
const TradeNotification tn = [&makerOrderID, &matchedPrice](OrderID makerID, OrderID takerOrderID, OrderStatus makerOrderStatus,
const auto tn = [&makerOrderID, &matchedPrice](OrderID makerID, OrderID takerOrderID, OrderStatus makerOrderStatus,
OrderStatus takerOrderStatus, Decimal matchedQty, Decimal priceValue) {
makerOrderID = makerID;
matchedPrice = priceValue;
};
const PostOrderFill pf = [&oq, &o1](OrderID id) {
const auto pf = [&oq, &o1](OrderID id) {
if (id == 1) {
oq->remove(&o1);
o1.id = 999;
Expand Down
Loading