From 3c8d82aa8613ae68ae98c352230900ff0f553a50 Mon Sep 17 00:00:00 2001 From: E Sequeira <5458743+geseq@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:14:04 +0000 Subject: [PATCH 1/3] perf: erase price-level rbtree node in O(1) via iterator_to remove() erased by key (price()), re-searching for a node already in hand. Erase via iterator_to(*q) instead (O(1) amortized), avoiding a redundant tree search on the cancel/fill path. append() is left on its original find()+insert_equal() body: a single-walk lower_bound()+hinted insert added a per-append Decimal equality compare on the common append-to-existing-level path, regressing the static workload. --- src/pricelevel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pricelevel.cpp b/src/pricelevel.cpp index 60e9701..0babbfe 100644 --- a/src/pricelevel.cpp +++ b/src/pricelevel.cpp @@ -52,7 +52,7 @@ void PriceLevel

::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); } From 1bd64b691d1df9f0717693588a9a96ca45370036 Mon Sep 17 00:00:00 2001 From: E Sequeira <5458743+geseq@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:17:11 +0000 Subject: [PATCH 2/3] perf: Lever A via function_ref (keeps callback bodies in .cpp) Alternative to the template approach: replace std::function callbacks with a non-owning function_ref (void* + static thunk, two pointers, no alloc). Methods stay non-template and their bodies remain in the .cpp; explicit instantiations unchanged. Removes per-order std::function construction but keeps an indirect call on dispatch (no inlining). Experiment branch for A/B vs the template variant. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_0196EKKWudtsg3gButufhDxz --- include/function_ref.hpp | 31 +++++++++++++++++++++++++++++++ include/orderqueue.hpp | 6 +++--- test/orderqueue_test.cpp | 16 ++++++++-------- 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 include/function_ref.hpp diff --git a/include/function_ref.hpp b/include/function_ref.hpp new file mode 100644 index 0000000..1977661 --- /dev/null +++ b/include/function_ref.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +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 function_ref; + +template +class function_ref { + void* obj_ = nullptr; + R (*thunk_)(void*, Args...) = nullptr; + + public: + template , function_ref>>> + function_ref(F&& f) noexcept + : obj_(const_cast(static_cast(std::addressof(f)))), + thunk_([](void* obj, Args... args) -> R { + return (*static_cast*>(obj))(std::forward(args)...); + }) {} + + R operator()(Args... args) const { return thunk_(obj_, std::forward(args)...); } +}; + +} // namespace orderbook diff --git a/include/orderqueue.hpp b/include/orderqueue.hpp index 20cf7a0..780fc02 100644 --- a/include/orderqueue.hpp +++ b/include/orderqueue.hpp @@ -1,17 +1,17 @@ #pragma once #include -#include #include "boost/intrusive/list.hpp" #include "boost/intrusive/set_hook.hpp" +#include "function_ref.hpp" #include "order.hpp" namespace orderbook { using TradeNotification = - std::function; -using PostOrderFill = std::function; + function_ref; +using PostOrderFill = function_ref; using OrderList = boost::intrusive::list; diff --git a/test/orderqueue_test.cpp b/test/orderqueue_test.cpp index eb822c1..1f8336c 100644 --- a/test/orderqueue_test.cpp +++ b/test/orderqueue_test.cpp @@ -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) { @@ -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) { @@ -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); @@ -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; From 9e9ad2dc3cd9b6ef88d24d0257f7528366cce1fe Mon Sep 17 00:00:00 2001 From: E Sequeira <5458743+geseq@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:08:32 +0000 Subject: [PATCH 3/3] build: enable LTO for Release builds and the benchmark adapter Turn on interprocedural optimization (-flto) for optimized builds of the orderbook library/main and the matching-engine benchmark adapter .so. Gated to Release-type builds via check_ipo_supported; sanitizer and Debug builds are unaffected. Enables cross-TU inlining of the matching chain. Measured (12-round interleaved A/B, cores 2/4): add/cancel throughput +42-44%; matching scenarios flat-to-positive; correctness unchanged (all harness scenarios PASS). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_0196EKKWudtsg3gButufhDxz --- CMakeLists.txt | 15 +++++++++++++++ bench/build.sh | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 665cff8..36b9047 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/bench/build.sh b/bench/build.sh index 607a6b8..7cca1be 100755 --- a/bench/build.sh +++ b/bench/build.sh @@ -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" \