[context review] Performance questions

I`ve read through the documentation of Boost.Context and some questions pop up: 1) What if we reimplement the ASIO example without Boost.Context (server class will look like this): class server : public boost::enable_shared_from_this< server > { private: boost::asio::ip::tcp::acceptor acceptor_; boost::array< char, 1024 > buffer_; void do_() { boost::asio::ip::tcp::socket socket( acceptor_.get_io_service() ); acceptor_.async_accept( socket, boost::bind( & server::do_1_, this->shared_from_this(), _1) ); } void do_1_(boost::system::error_code ec) { if (!ec) { socket.async_read_some( boost::asio::buffer(buffer_), boost::bind( & server::do_2_, this->shared_from_this(), _1, _2) ); } else { do_(); } } void do_2_(boost::system::error_code ec, size_t n) if (!ec) { boost::asio::async_write( socket, boost::asio::buffer( buffer, n), boost::bind( & server::do_1_, this->shared_from_this(), _1) ); } else { do_(); } } } server( boost::asio::io_service & io_service, short port) : acceptor_( io_service, boost::asio::ip::tcp::endpoint( boost::asio::ip::tcp::v4(), port) ), {} public: typedef boost::shared_ptr< server > ptr_t; static ptr_t create( boost::asio::io_service & io_service, short port) { return ptr_t( new server( io_service, port) ); } void operator()( boost::system::error_code /*ec*/, size_t /*n*/) { do_(); } }; Looks like this implementation will be faster (it has the same amount of io_service posted functions, no dynamic allocations, but has no context switches). Am I missing something? 2) Can we use contexts like condition variables? For example: struct task{ }; std::deque void thread_1() void threads

2) Can we use contexts like condition variables? Can we make something like this: struct task{ // Implementanion }; std::deque<task> g_tasks; continuation g_cont; boost::thread::mutex g_mutex; // Executes ready tasks void thread_1() { // This function will be run in thread #1 while(1) { scoped_lock lock(g_mutex); if (g_tasks.empty()) { lock.unlock(); g_cont.suspend(); } else { auto t = g_tasks.front(); g_tasks.pop_front(); lock.unlock(); t.run(); } } } // Generate tasks void threads_2_n() { // This function will be run in threads #2, #3 ... while(1) { // Generating task t task t; t.generate(); scoped_lock lock(g_mutex); g_tasks.push_back(t); lock.unlock(); g_cont.resume(); } } Will there be some performance gains, because there is no more system calls for condition variables? If answer is yes for 2), then it is a killer feature and I will vote YES and write a more detailed review. P.S.: Sorry for two mails, instead of one. Best regards, Antony Polukhin

Am 09.01.2012 19:54, schrieb Antony Polukhin:
2) Can we use contexts like condition variables? Can we make something like this:
struct task{ // Implementanion };
std::deque<task> g_tasks; continuation g_cont; boost::thread::mutex g_mutex;
<snip> boost.context isn't related to threads. It doesn't deal with context switches related to threads (the kernel doesn't see switches done by boost.context). from docu: 'A context switch between threads requires system calls (involving the OS kernel), ... By contrast, transferring control among boost.context requires only a few hundred CPU cycles because it does not involve system calls as it is done within a single thread.' Oliver

Review Here are some small remarks: 1) Section "Struct boost_fcontext_t and related functions" misses header name, in which C interface is described. 2) Header context/detail/context_base.hpp includes # include <boost/context/detail/context_base_fiber.hpp> # include <boost/context/detail/context_base_uctx.hpp> This headers do not exist in checkout. 3) Moving context between threads is described in documentation but is not tested in tests. 4) Rvalue references sometimes miss forward(). For example bost/context/context.hpp (line 60) must be like: template< typename Fn, typename Allocator > static base_ptr_t make_context_( Fn && fn, std::size_t size, flag_unwind_t do_unwind, flag_return_t do_return, Allocator const& alloc) { return base_ptr_t( new detail::context_object< typename remove_reference< Fn
::type, Allocator >( boost::forward<Fn>(fn), alloc, size, do_unwind, do_return) ); }
Also forward() calls are required on lines 76, 151, 156, 161 (instead of static_cast< &&>) Preprocessor generated constructors can also have their parameters forwarded. Test cases can be add for this (create function object that is only movable, and move it to the context instance) I`ve spend 2-3 hours looking through the code. Code looks good. Did not build or run tests or examples. Acceptance conditions have been met. I vote "yes". One more question: does ARM implementation works correctly with/without VFP registers/floating point support? Best regards, Antony Polukhin

Hi Antony Am 10.01.2012 19:32, schrieb Antony Polukhin:
1) Section "Struct boost_fcontext_t and related functions" misses header name, in which C interface is described. OK
2) Header context/detail/context_base.hpp includes # include<boost/context/detail/context_base_fiber.hpp> # include<boost/context/detail/context_base_uctx.hpp> This headers do not exist in checkout. OK
3) Moving context between threads is described in documentation but is not tested in tests. I'm not shure if it should be tested - it's not the focus of the lib?!
4) Rvalue references sometimes miss forward(). For example bost/context/context.hpp (line 60) must be like:
template< typename Fn, typename Allocator> static base_ptr_t make_context_( Fn&& fn, std::size_t size, flag_unwind_t do_unwind, flag_return_t do_return, Allocator const& alloc) { return base_ptr_t( new detail::context_object< typename remove_reference< Fn
::type, Allocator>( boost::forward<Fn>(fn), alloc, size, do_unwind, do_return) ); }
Also forward() calls are required on lines 76, 151, 156, 161 (instead of static_cast< &&>) Preprocessor generated constructors can also have their parameters forwarded. Test cases can be add for this (create function object that is only movable, and move it to the context instance) I was not aware of forward() - which boost lib contains it?
One more question: does ARM implementation works correctly with/without VFP registers/floating point support? well - at least I tried to accomplish the ARM calling standard for LINUX and because I've no ARM board I was forced to test the code on qemu-arm (ARM926EJ-S). Related to VFP registers/floating point support: AFAIK LINUX does allways software FP but I took the recommendations of the Debian people (ARM port) into account (#if (defined(__VFP_FP__) && !defined(__SOFTFP__))).
regards, Oliver

4) Rvalue references sometimes miss forward(). For example bost/context/context.hpp (line 60) must be like:
template< typename Fn, typename Allocator> static base_ptr_t make_context_( Fn&& fn, std::size_t size, flag_unwind_t do_unwind, flag_return_t do_return, Allocator const& alloc) { return base_ptr_t( new detail::context_object< typename remove_reference< Fn
::type, Allocator>( boost::forward(fn), alloc, size, do_unwind, do_return) ); }
Also forward() calls are required on lines 76, 151, 156, 161 (instead of static_cast< &&>) Preprocessor generated constructors can also have their parameters forwarded. Test cases can be add for this (create function object that is only movable, and move it to the context instance) I was not aware of forward() - which boost lib contains it?
std::forward is in <utility>. I didn't know there was a boost::forward - what is the purpose of it? Regards, Nate

2012/1/10 Nathan Ridge <zeratul976@hotmail.com>:
std::forward is in <utility>.
I didn't know there was a boost::forward - what is the purpose of it?
Regards, Nate
It is a part of Boost.Move library http://www.boost.org/doc/libs/1_48_0/doc/html/boost/forward.html . Works like std::forward, when there are rvalues, otherwise tries to emulate them. 2012/1/10 Oliver Kowalke <oliver.kowalke@gmx.de>:
Am 10.01.2012 19:32, schrieb Antony Polukhin:
3) Moving context between threads is described in documentation but is not tested in tests.
I'm not shure if it should be tested - it's not the focus of the lib?!
May be you are right. I`ve proposed it, because it potentially can give some better results in error detections (For example you switch contexts, do some work, than switch back. There is a small chance, that some data/register was not stored in context and was not changed between context switches. When you move context to another thread, there is almost no chance that data/register in different threads will match, so there will be easier to detect error/unstored data.) Best regards, Antony Polukhin

Am 09.01.2012 19:35, schrieb Antony Polukhin:
1) What if we reimplement the ASIO example without Boost.Context (server class will look like this):
class server : public boost::enable_shared_from_this< server> { private: boost::asio::ip::tcp::acceptor acceptor_; boost::array< char, 1024> buffer_;
void do_() { boost::asio::ip::tcp::socket socket( acceptor_.get_io_service() ); acceptor_.async_accept( socket, boost::bind(& server::do_1_, this->shared_from_this(), _1) ); }
void do_1_(boost::system::error_code ec) { if (!ec) { socket.async_read_some( boost::asio::buffer(buffer_), boost::bind(& server::do_2_, this->shared_from_this(), _1, _2) ); } else { do_(); } }
void do_2_(boost::system::error_code ec, size_t n) if (!ec) { boost::asio::async_write( socket, boost::asio::buffer( buffer, n), boost::bind(& server::do_1_, this->shared_from_this(), _1) ); } else { do_(); } } }
server( boost::asio::io_service& io_service, short port) : acceptor_( io_service, boost::asio::ip::tcp::endpoint( boost::asio::ip::tcp::v4(), port) ), {}
public: typedef boost::shared_ptr< server> ptr_t;
static ptr_t create( boost::asio::io_service& io_service, short port) { return ptr_t( new server( io_service, port) ); }
void operator()( boost::system::error_code /*ec*/, size_t /*n*/) { do_(); } };
Looks like this implementation will be faster (it has the same amount of io_service posted functions, no dynamic allocations, but has no context switches). Am I missing something?
Beside the stack allocation (which could be preallocated and/or reused) the boost.context function will not be slower. With 'context switches' I don't mean context switches in the case of threads! 'context switches' in boost.context consist of saving/restoring relevant registers like stack pointer, instruction pointer, non-scratch registers etc = the code generated by the compiler does the same (which registers are saved/restored by a function call is defined by the 'call convention' like cdecl etc.). The ASIO example using boost.context is equivalent to using from functions do_<xyz>_() in this sense. The benefit in using boost.context is, that the code can be written more straight forward. Your logic isn't scattered among several functions (do_<xyz>_()). I think Chris might explain this issue better than me in its article 'Thinking Asynchronously in C++' at http://blog.think-async.com/2009/08/secret-sauce-revealed.html. I adopted Chris example. Oliver
participants (3)
-
Antony Polukhin
-
Nathan Ridge
-
Oliver Kowalke