diff --git a/doc/unlisted/execution-contexts.adoc b/doc/unlisted/execution-contexts.adoc index 40981841e..37ae26850 100644 --- a/doc/unlisted/execution-contexts.adoc +++ b/doc/unlisted/execution-contexts.adoc @@ -271,13 +271,6 @@ Service management functions (`use_service`, `make_service`, `find_service`) are thread-safe. The `shutdown()` and `destroy()` functions are NOT thread-safe and must only be called during destruction. -=== Performance - -The first 32 distinct service types registered across the program benefit from -an O(1) lock-free fast path for `find_service` and `use_service`. Beyond 32 -types, lookups fall back to a mutex-protected linear scan. In practice, 32 -slots is sufficient for any realistic program. - == When NOT to Use execution_context Directly Use `execution_context` directly when: diff --git a/include/boost/capy/detail/service_slot.hpp b/include/boost/capy/detail/service_slot.hpp deleted file mode 100644 index 22541070e..000000000 --- a/include/boost/capy/detail/service_slot.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2026 Michael Vandeberg -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/capy -// - -#ifndef BOOST_CAPY_DETAIL_SERVICE_SLOT_HPP -#define BOOST_CAPY_DETAIL_SERVICE_SLOT_HPP - -#include -#include - -namespace boost { -namespace capy { -namespace detail { - -/* Slot ID infrastructure for O(1) service lookup. - - Each distinct service type T gets a unique integer index via - service_slot(). The index is assigned on first call from a - global atomic counter and cached in a function-local static. - Cross-DLL safety relies on COMDAT deduplication (same mechanism - as type_id_impl::tag). -*/ - -inline std::atomic next_service_slot{0}; - -template -std::size_t -service_slot() noexcept -{ - static const std::size_t id = - next_service_slot.fetch_add(1, std::memory_order_relaxed); - return id; -} - -} // namespace detail -} // namespace capy -} // namespace boost - -#endif diff --git a/include/boost/capy/ex/execution_context.hpp b/include/boost/capy/ex/execution_context.hpp index ee28eba75..1000dec73 100644 --- a/include/boost/capy/ex/execution_context.hpp +++ b/include/boost/capy/ex/execution_context.hpp @@ -12,10 +12,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -225,14 +223,6 @@ class BOOST_CAPY_DECL template T* find_service() const noexcept { - auto id = detail::service_slot(); - if(id < max_service_slots) - { - auto* p = slots_[id].load( - std::memory_order_acquire); - if(p) - return static_cast(p); - } std::lock_guard lock(mutex_); return static_cast(find_impl(detail::type_id())); } @@ -265,24 +255,6 @@ class BOOST_CAPY_DECL "T must derive from service"); static_assert(std::is_constructible::value, "T must be constructible from execution_context&"); - if constexpr(get_key::value) - { - static_assert( - std::is_convertible::type&>::value, - "T& must be convertible to key_type&"); - } - - // Fast path: O(1) slot lookup - { - auto id = detail::service_slot(); - if(id < max_service_slots) - { - auto* p = slots_[id].load( - std::memory_order_acquire); - if(p) - return static_cast(*p); - } - } struct impl : factory { @@ -291,11 +263,7 @@ class BOOST_CAPY_DECL detail::type_id(), get_key::value ? detail::type_id::type>() - : detail::type_id(), - detail::service_slot(), - get_key::value - ? detail::service_slot::type>() - : detail::service_slot()) + : detail::type_id()) { } @@ -357,11 +325,7 @@ class BOOST_CAPY_DECL detail::type_id(), get_key::value ? detail::type_id::type>() - : detail::type_id(), - detail::service_slot(), - get_key::value - ? detail::service_slot::type>() - : detail::service_slot()) + : detail::type_id()) , args_(std::forward(a)...) { } @@ -541,16 +505,11 @@ class BOOST_CAPY_DECL detail::type_index t0; detail::type_index t1; BOOST_CAPY_MSVC_WARNING_POP - std::size_t slot0; - std::size_t slot1; factory( detail::type_info const& t0_, - detail::type_info const& t1_, - std::size_t s0, - std::size_t s1) + detail::type_info const& t1_) : t0(t0_), t1(t1_) - , slot0(s0), slot1(s1) { } @@ -564,7 +523,7 @@ class BOOST_CAPY_DECL service& use_service_impl(factory& f); service& make_service_impl(factory& f); -// warning C4251: std::mutex, std::shared_ptr, std::atomic need dll-interface +// warning C4251: std::mutex, std::shared_ptr need dll-interface BOOST_CAPY_MSVC_WARNING_PUSH BOOST_CAPY_MSVC_WARNING_DISABLE(4251) mutable std::mutex mutex_; @@ -573,12 +532,6 @@ class BOOST_CAPY_DECL std::pmr::memory_resource* frame_alloc_ = nullptr; service* head_ = nullptr; bool shutdown_ = false; - - static constexpr std::size_t max_service_slots = 32; - BOOST_CAPY_MSVC_WARNING_PUSH - BOOST_CAPY_MSVC_WARNING_DISABLE(4251) - std::atomic slots_[max_service_slots] = {}; - BOOST_CAPY_MSVC_WARNING_POP }; template< typename Derived > diff --git a/src/ex/execution_context.cpp b/src/ex/execution_context.cpp index 6da375cba..c1268d7e6 100644 --- a/src/ex/execution_context.cpp +++ b/src/ex/execution_context.cpp @@ -55,8 +55,6 @@ destroy() noexcept delete p; p = next; } - for(auto& s : slots_) - s.store(nullptr, std::memory_order_relaxed); } execution_context::service* @@ -80,13 +78,7 @@ use_service_impl(factory& f) std::unique_lock lock(mutex_); if(auto* p = find_impl(f.t0)) - { - if(f.slot0 < max_service_slots) - slots_[f.slot0].store(p, std::memory_order_release); - if(f.slot0 != f.slot1 && f.slot1 < max_service_slots) - slots_[f.slot1].store(p, std::memory_order_release); return *p; - } lock.unlock(); @@ -99,10 +91,6 @@ use_service_impl(factory& f) if(auto* p = find_impl(f.t0)) { - if(f.slot0 < max_service_slots) - slots_[f.slot0].store(p, std::memory_order_release); - if(f.slot0 != f.slot1 && f.slot1 < max_service_slots) - slots_[f.slot1].store(p, std::memory_order_release); delete sp; return *p; } @@ -110,11 +98,6 @@ use_service_impl(factory& f) sp->next_ = head_; head_ = sp; - if(f.slot0 < max_service_slots) - slots_[f.slot0].store(sp, std::memory_order_release); - if(f.slot0 != f.slot1 && f.slot1 < max_service_slots) - slots_[f.slot1].store(sp, std::memory_order_release); - return *sp; } @@ -158,11 +141,6 @@ make_service_impl(factory& f) p->next_ = head_; head_ = p; - if(f.slot0 < max_service_slots) - slots_[f.slot0].store(p, std::memory_order_release); - if(f.slot0 != f.slot1 && f.slot1 < max_service_slots) - slots_[f.slot1].store(p, std::memory_order_release); - return *p; } diff --git a/test/unit/ex/execution_context.cpp b/test/unit/ex/execution_context.cpp index fa896f27f..9bed2ffcf 100644 --- a/test/unit/ex/execution_context.cpp +++ b/test/unit/ex/execution_context.cpp @@ -387,85 +387,6 @@ struct execution_context_test BOOST_TEST_NE(new_mr, default_mr); } - void - testSlotLookupConsistency() - { - // Verify that find_service returns the same pointer - // whether from the slot fast path or linked list fallback. - test_io_context ctx; - - auto& svc = ctx.make_service(77); - auto* p1 = ctx.find_service(); - auto* p2 = ctx.find_service(); - - BOOST_TEST_NE(p1, nullptr); - BOOST_TEST_EQ(p1, p2); - BOOST_TEST_EQ(p1, &svc); - } - - void - testSlotKeyTypeLookup() - { - // Verify slot lookup works for both concrete and key_type. - test_io_context ctx; - - ctx.make_service(55); - - auto* p1 = ctx.find_service(); - BOOST_TEST_NE(p1, nullptr); - BOOST_TEST_EQ(p1->value, 55); - - auto* p2 = ctx.find_service(); - BOOST_TEST_NE(p2, nullptr); - BOOST_TEST_EQ(p2->get_value(), 55); - - // Both should point to the same object - BOOST_TEST_EQ( - static_cast(p1), p2); - } - - void - testUseServiceSlotFastPath() - { - // Verify use_service fast path returns same instance. - test_io_context ctx; - - auto& svc1 = ctx.use_service(); - auto& svc2 = ctx.use_service(); - - BOOST_TEST_EQ(&svc1, &svc2); - } - - void - testConcurrentUseServiceSlots() - { - // Stress test: many threads calling use_service simultaneously. - // All must get the same service instance. - test_io_context ctx; - constexpr int num_threads = 16; - std::atomic results[num_threads] = {}; - - std::vector threads; - threads.reserve(num_threads); - - for(int i = 0; i < num_threads; ++i) - { - threads.emplace_back([&ctx, &results, i]{ - auto& svc = ctx.use_service(); - results[i].store(&svc, - std::memory_order_relaxed); - }); - } - - for(auto& t : threads) - t.join(); - - auto* expected = results[0].load(); - BOOST_TEST_NE(expected, nullptr); - for(int i = 1; i < num_threads; ++i) - BOOST_TEST_EQ(results[i].load(), expected); - } - void run() { @@ -485,10 +406,6 @@ struct execution_context_test testGetFrameAllocator(); testSetFrameAllocatorRawPointer(); testSetFrameAllocatorTemplate(); - testSlotLookupConsistency(); - testSlotKeyTypeLookup(); - testUseServiceSlotFastPath(); - testConcurrentUseServiceSlots(); } };