diff --git a/doc/performance.qbk b/doc/performance.qbk index 2ad7f597..998fa812 100644 --- a/doc/performance.qbk +++ b/doc/performance.qbk @@ -17,7 +17,7 @@ average computed over 1000 iterations. [table Perfomance of coroutine switch [[thread] [qthread] [tbb] [fiber]] - [[37114 ns] [577 ns] [309 ns] [1124 ns]] + [[37114 ns] [577 ns] [309 ns] [1063 ns]] ] diff --git a/include/boost/fiber/detail/fiber_base.hpp b/include/boost/fiber/detail/fiber_base.hpp index 99e528c6..43b11d3b 100644 --- a/include/boost/fiber/detail/fiber_base.hpp +++ b/include/boost/fiber/detail/fiber_base.hpp @@ -13,9 +13,7 @@ #include #include -#include #include -#include #include #include #include @@ -42,8 +40,6 @@ namespace boost { namespace fibers { namespace detail { -namespace coro = boost::coroutines; - class BOOST_FIBERS_DECL fiber_base : public notify { public: @@ -81,50 +77,18 @@ private: fss_data_t fss_data_; - // set terminate is only set inside fiber_base::trampoline_() - void set_terminated_() BOOST_NOEXCEPT; - - void trampoline_( coro::symmetric_coroutine< void >::yield_type &); - protected: - atomic< state_t > state_; - atomic< int > flags_; - atomic< int > priority_; - coro::symmetric_coroutine< void >::yield_type * callee_; - coro::symmetric_coroutine< void >::call_type caller_; - exception_ptr except_; - spinlock splk_; - std::vector< ptr_t > waiting_; + atomic< state_t > state_; + atomic< int > flags_; + atomic< int > priority_; + exception_ptr except_; + spinlock splk_; + std::vector< ptr_t > waiting_; void release(); - virtual void run() = 0; - public: - template< typename StackAllocator, typename Allocator > - fiber_base( attributes const& attrs, StackAllocator const& stack_alloc, Allocator const&) : - fss_data_(), - state_( READY), - flags_( 0), - priority_( 0), - callee_( 0), - caller_( - boost::bind( & fiber_base::trampoline_, this, _1), - attrs, - stack_alloc), - except_(), - waiting_() - { - BOOST_ASSERT( caller_); - BOOST_ASSERT( 0 == callee_); - - caller_(); // jump to trampoline - - BOOST_ASSERT( 0 != callee_); - BOOST_ASSERT( * callee_); - - set_ready(); // fiber is setup and now ready to run - } + fiber_base(); virtual ~fiber_base(); @@ -137,10 +101,6 @@ public: void priority( int prio) BOOST_NOEXCEPT { priority_ = prio; } - void resume(); - - void suspend(); - bool join( ptr_t const&); bool interruption_blocked() const BOOST_NOEXCEPT @@ -170,6 +130,8 @@ public: bool is_waiting() const BOOST_NOEXCEPT { return WAITING == state_; } + void set_terminated() BOOST_NOEXCEPT; + void set_ready() BOOST_NOEXCEPT; void set_running() BOOST_NOEXCEPT; @@ -188,6 +150,10 @@ public: { return except_; } void rethrow() const; + + virtual void resume() = 0; + + virtual void suspend() = 0; }; }}} diff --git a/include/boost/fiber/detail/fiber_object.hpp b/include/boost/fiber/detail/fiber_object.hpp index 70854887..0176ae88 100644 --- a/include/boost/fiber/detail/fiber_object.hpp +++ b/include/boost/fiber/detail/fiber_object.hpp @@ -10,13 +10,17 @@ #include #include +#include #include +#include +#include #include #include #include #include #include +#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -31,19 +35,26 @@ namespace boost { namespace fibers { namespace detail { -template< typename Fn, typename Allocator > +namespace coro = boost::coroutines; + +template< typename Fn, typename StackAllocator, typename Allocator > class fiber_object : public fiber_base { public: typedef typename Allocator::template rebind< - fiber_object< Fn, Allocator > - >::other allocator_t; + fiber_object< Fn, StackAllocator, Allocator > + >::other allocator_t; private: - typedef fiber_base base_type; + typedef fiber_base base_t; + typedef coro::symmetric_coroutine< + void, StackAllocator + > coro_t; - Fn fn_; - allocator_t alloc_; + Fn fn_; + typename coro_t::yield_type * callee_; + typename coro_t::call_type caller_; + allocator_t alloc_; static void destroy_( allocator_t & alloc, fiber_object * p) { @@ -54,41 +65,131 @@ private: fiber_object( fiber_object &); fiber_object & operator=( fiber_object const&); + void trampoline_( typename coro_t::yield_type & yield) + { + BOOST_ASSERT( yield); + BOOST_ASSERT( ! is_terminated() ); + + callee_ = & yield; + set_running(); + suspend(); + + try + { + BOOST_ASSERT( is_running() ); + fn_(); + BOOST_ASSERT( is_running() ); + } + catch ( coro::detail::forced_unwind const&) + { + set_terminated(); + release(); + throw; + } + catch ( fiber_interrupted const&) + { except_ = current_exception(); } + catch (...) + { std::terminate(); } + + set_terminated(); + release(); + suspend(); + + BOOST_ASSERT_MSG( false, "fiber already terminated"); + } + public: #ifndef BOOST_NO_RVALUE_REFERENCES - template< typename StackAllocator > - fiber_object( Fn && fn, attributes const& attr, + fiber_object( Fn && fn, attributes const& attrs, StackAllocator const& stack_alloc, allocator_t const& alloc) : - base_type( attr, stack_alloc, alloc), + base_t(), fn_( forward< Fn >( fn) ), + callee_( 0), + caller_( + boost::bind( & fiber_object::trampoline_, this, _1), + attrs, + stack_alloc), alloc_( alloc) - {} -#else - template< typename StackAllocator > - fiber_object( Fn fn, attributes const& attr, - StackAllocator const& stack_alloc, - allocator_t const& alloc) : - base_type( attr, stack_alloc, alloc), - fn_( fn), - alloc_( alloc) - {} + { + BOOST_ASSERT( caller_); + BOOST_ASSERT( 0 == callee_); - template< typename StackAllocator > - fiber_object( BOOST_RV_REF( Fn) fn, attributes const& attr, + caller_(); // jump to trampoline + + BOOST_ASSERT( 0 != callee_); + BOOST_ASSERT( * callee_); + + set_ready(); // fiber is setup and now ready to run + } +#else + fiber_object( Fn fn, attributes const& attrs, StackAllocator const& stack_alloc, allocator_t const& alloc) : - base_type( attr, stack_alloc, alloc), + base_t(), fn_( fn), + callee_( 0), + caller_( + boost::bind( & fiber_object::trampoline_, this, _1), + attrs, + stack_alloc), alloc_( alloc) - {} + { + BOOST_ASSERT( caller_); + BOOST_ASSERT( 0 == callee_); + + caller_(); // jump to trampoline + + BOOST_ASSERT( 0 != callee_); + BOOST_ASSERT( * callee_); + + set_ready(); // fiber is setup and now ready to run + } + + fiber_object( BOOST_RV_REF( Fn) fn, attributes const& attrs, + StackAllocator const& stack_alloc, + allocator_t const& alloc) : + base_t(), + fn_( fn), + callee_( 0), + caller_( + boost::bind( & fiber_object::trampoline_, this, _1), + attrs, + stack_alloc), + alloc_( alloc) + { + BOOST_ASSERT( caller_); + BOOST_ASSERT( 0 == callee_); + + caller_(); // jump to trampoline + + BOOST_ASSERT( 0 != callee_); + BOOST_ASSERT( * callee_); + + set_ready(); // fiber is setup and now ready to run + } #endif void deallocate_object() { destroy_( alloc_, this); } - void run() - { fn_(); } + void resume() + { + BOOST_ASSERT( caller_); + BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm + + caller_(); + } + + void suspend() + { + BOOST_ASSERT( callee_); + BOOST_ASSERT( * callee_); + + ( * callee_)(); + + BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm + } }; }}} diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 1babcfa7..9accd97e 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -77,7 +77,7 @@ public: impl_() { typedef detail::fiber_object< - fiber_fn, std::allocator< fiber > + fiber_fn, stack_allocator, std::allocator< fiber > > object_t; object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -93,7 +93,7 @@ public: impl_() { typedef detail::fiber_object< - fiber_fn, std::allocator< fiber > + fiber_fn, StackAllocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -109,7 +109,7 @@ public: impl_() { typedef detail::fiber_object< - fiber_fn, Allocator + fiber_fn, StackAllocator, Allocator > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -125,7 +125,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, stack_allocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -141,7 +141,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, StackAllocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -157,7 +157,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, Allocator + Fn, StackAllocator, Allocator > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -173,7 +173,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, stack_allocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -189,7 +189,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, StackAllocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -205,7 +205,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, Allocator + Fn, StackAllocator, Allocator > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -221,7 +221,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, stack_allocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -237,7 +237,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, std::allocator< fiber > + Fn, StackAllocator, std::allocator< fiber > > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( @@ -253,7 +253,7 @@ public: impl_() { typedef detail::fiber_object< - Fn, Allocator + Fn, StackAllocator, Allocator > object_t; typename object_t::allocator_t a( alloc); impl_ = ptr_t( diff --git a/performance/fiber/test_fiber.cpp b/performance/fiber/test_fiber.cpp index 79845fff..46e110cb 100644 --- a/performance/fiber/test_fiber.cpp +++ b/performance/fiber/test_fiber.cpp @@ -16,21 +16,24 @@ #include "../bind_processor.hpp" #include "../clock.hpp" +#include "../preallocated_stack_allocator.hpp" boost::coroutines::flag_fpu_t preserve_fpu = boost::coroutines::fpu_not_preserved; boost::coroutines::flag_unwind_t unwind_stack = boost::coroutines::no_stack_unwind; boost::uint64_t jobs = 1000; +bool prealloc = true; void worker() {} -duration_type measure( duration_type overhead) +template< typename StackAllocator > +duration_type measure( duration_type overhead, StackAllocator const& stack_alloc) { boost::fibers::attributes attrs( unwind_stack, preserve_fpu); - boost::fibers::fiber( worker, attrs).join(); + boost::fibers::fiber( worker, attrs, stack_alloc).join(); time_point_type start( clock_type::now() ); for ( std::size_t i = 0; i < jobs; ++i) { - boost::fibers::fiber( worker, attrs).join(); + boost::fibers::fiber( worker, attrs, stack_alloc).join(); } duration_type total = clock_type::now() - start; total -= overhead_clock(); // overhead of measurement @@ -39,6 +42,20 @@ duration_type measure( duration_type overhead) return total; } +duration_type measure_standard( duration_type overhead) +{ + boost::fibers::stack_allocator stack_alloc; + + return measure( overhead, stack_alloc); +} + +duration_type measure_prealloc( duration_type overhead) +{ + preallocated_stack_allocator stack_alloc; + + return measure( overhead, stack_alloc); +} + int main( int argc, char * argv[]) { try @@ -50,6 +67,7 @@ int main( int argc, char * argv[]) ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") ("unwind,u", boost::program_options::value< bool >( & unwind), "unwind fiber-stack") + ("prealloc,p", boost::program_options::value< bool >( & prealloc), "use preallocated stack") ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); boost::program_options::variables_map vm; @@ -72,7 +90,10 @@ int main( int argc, char * argv[]) duration_type overhead = overhead_clock(); std::cout << "overhead " << overhead.count() << " nano seconds" << std::endl; - boost::uint64_t res = measure( overhead).count(); + boost::uint64_t res = + prealloc + ? measure_prealloc( overhead).count() + : measure_standard( overhead).count(); std::cout << "average of " << res << " nano seconds" << std::endl; return EXIT_SUCCESS; diff --git a/performance/preallocated_stack_allocator.hpp b/performance/preallocated_stack_allocator.hpp new file mode 100644 index 00000000..a2db1436 --- /dev/null +++ b/performance/preallocated_stack_allocator.hpp @@ -0,0 +1,63 @@ + +// Copyright Oliver Kowalke 2009. +// 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) + +#ifndef BOOST_FIBER_PREALLOCATED_STACK_ALLOCATOR_H +#define BOOST_FIBER_PREALLOCATED_STACK_ALLOCATOR_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +class preallocated_stack_allocator +{ +private: + boost::coroutines::stack_context stack_ctx_; + +public: + preallocated_stack_allocator() : + stack_ctx_() + { + boost::coroutines::standard_stack_allocator allocator; + allocator.allocate( stack_ctx_, default_stacksize() ); + } + + void allocate( boost::coroutines::stack_context & ctx, std::size_t size) + { + ctx.sp = stack_ctx_.sp; + ctx.size = stack_ctx_.size; + } + + void deallocate( boost::coroutines::stack_context & ctx) + { + } + + static std::size_t maximum_stacksize() + { return boost::coroutines::standard_stack_allocator::maximum_stacksize(); } + + static std::size_t default_stacksize() + { return boost::coroutines::standard_stack_allocator::default_stacksize(); } + + static std::size_t minimum_stacksize() + { return boost::coroutines::standard_stack_allocator::minimum_stacksize(); } +}; + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBER_PREALLOCATED_STACK_ALLOCATOR_H diff --git a/src/detail/fiber_base.cpp b/src/detail/fiber_base.cpp index 9aadf54e..4f4e6ce1 100644 --- a/src/detail/fiber_base.cpp +++ b/src/detail/fiber_base.cpp @@ -23,46 +23,14 @@ namespace boost { namespace fibers { namespace detail { -void -fiber_base::set_terminated_() BOOST_NOEXCEPT -{ - state_t previous = state_.exchange( TERMINATED); - BOOST_ASSERT( RUNNING == previous); -} - -void -fiber_base::trampoline_( coro::symmetric_coroutine< void >::yield_type & yield) -{ - BOOST_ASSERT( yield); - BOOST_ASSERT( ! is_terminated() ); - - callee_ = & yield; - set_running(); - suspend(); - - try - { - BOOST_ASSERT( is_running() ); - run(); - BOOST_ASSERT( is_running() ); - } - catch ( coro::detail::forced_unwind const&) - { - set_terminated_(); - release(); - throw; - } - catch ( fiber_interrupted const&) - { except_ = current_exception(); } - catch (...) - { std::terminate(); } - - set_terminated_(); - release(); - suspend(); - - BOOST_ASSERT_MSG( false, "fiber already terminated"); -} +fiber_base::fiber_base() : + fss_data_(), + state_( READY), + flags_( 0), + priority_( 0), + except_(), + waiting_() +{} fiber_base::~fiber_base() { @@ -70,26 +38,6 @@ fiber_base::~fiber_base() BOOST_ASSERT( waiting_.empty() ); } -void -fiber_base::resume() -{ - BOOST_ASSERT( caller_); - BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm - - caller_(); -} - -void -fiber_base::suspend() -{ - BOOST_ASSERT( callee_); - BOOST_ASSERT( * callee_); - - ( * callee_)(); - - BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm -} - void fiber_base::release() { @@ -147,6 +95,13 @@ fiber_base::thread_affinity( bool req) BOOST_NOEXCEPT flags_ &= ~flag_thread_affinity; } +void +fiber_base::set_terminated() BOOST_NOEXCEPT +{ + state_t previous = state_.exchange( TERMINATED); + BOOST_ASSERT( RUNNING == previous); +} + void fiber_base::set_ready() BOOST_NOEXCEPT {