maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #04337
Re: [Commits] Rev 3020: MWL#192: Non-blocking client API for libmysqlclient. in http://bazaar.launchpad.net/~maria-captains/maria/5.2
MARK CALLAGHAN <mdcallag@xxxxxxxxx> writes:
> Why does this need to save/restore thread contexts (setcontext, etc)?
> I think that if the library is that hard to change then it should be
> fixed or a simpler solution should be attempted.
In general, when implementing non-blocking semantics like this, there are two
main approaches:
1. Write the code in event-driven style. This means using some kind of state
machine or message passing (or both) rather than normal nested
calls. Whenever code needs to do I/O, or call another function that might
do I/O, the current state is manually saved to some struct and control
returns to the caller. When the I/O completes control must continue in the
next state of the state machine or handler of completion message.
2. Use co-routines (also called fibers or light-weight threads or
whatever). The code is written in normal style with nested calls. For
operations that need to do I/O, a co-routine is spawned to run the
operation; when waiting for I/O the co-routine is suspended, and resumed
again when I/O completes.
The main reason I choose (2) is similar to what RethinkDB describe here:
http://blog.rethinkdb.com/improving-a-large-c-project-with-coroutines
http://blog.rethinkdb.com/making-coroutines-fast
Basically, (1) is nice for writing quick IRC bots and the like, but as the
complexity of the problem grows, the event-driven code becomes very hard to
maintain and extend.
For adding non-blocking to an existing library like libmysql, there is the
added advantage that the change can be much less intrusive, as we can avoid
re-writing parts of the existing code into event-driven style.
As also discussed in the RethinkDB blogs, one needs to be aware of
performance, as some co-routine implementations (eg. POSIX ucontext) are
inefficient. The use of co-routines in non-blocking libmysqlclient is
particularly simple, so I added simple optimised implementation for i386 and
x86_64, and benchmarked them.
Micro-benchmark on x86_64 shows that the cost of spawning a co-routine
(compared to a direct function call) is about 12 cycles (64-bit Intel Sandy
Bridge CPU). That is quite low. It's about the same cost as a pipeline stall
(eg. a single mispredicted branch). I think one would be hard pressed to
achieve such low overhead from the state machine/message passing needed for
event-driven style (1).
I also benchmarked real library usage: fetch 100,000,000 rows using
mysql_use_result()+mysql_fetch_row(). Rows have a single constant integer
value pulled from a self join of MEMORY tables:
SELECT 1 from t1 a CROSS JOIN t1 b CROSS JOIN t1 c CROSS JOIN ...
Connection is using localhost unix socket to minimise network overhead. This
is about the worst-case scenario, with a co-routine spawn for every row
fetched. On my machine, this can fetch around 10,000,000 rows per second. Even
in this worst-case scenario, it is impossible to measure any difference
between the normal blocking code and the non-blocking code using co-routines,
as the server is the bottleneck.
In short, option (2) is not "needed", but I think it is the best and simplest
choice.
Hope this helps,
- Kristian.
Follow ups
References