Updated C client, performance, etc...
Sean Chittenden
sean at chittenden.org
Fri Nov 12 10:56:28 PST 2004
>> *) Using kqueue(2) instead of select(2) would probably be a nice
>> winner, but I don't want to deal with interface portability quite yet
>> (besides, one file descriptor on a select(2) call isn't too terrible).
>
> Note that anyone using memcached has already figured out wrangling
> libevent. But why are you using select at all?
Needed a cheap and easy way to prevent sucking up cycles in the kernel.
non-blocking IO + select() gives me that.
> Looking through the
> code, it looks like you're using non-blocking sockets?
In part of the code, yes.
> Why?
When reading responses from the server, I use non-blocking IO to piece
together a buffer that contains a full line of data. If I don't have a
full line of data, I go back and try and read another chunk of data.
If I use blocking sockets, if I try and read(2) too much or too little,
I'm screwed and the client hangs.
Right now I read(2) a bit of data into a buffer. If it completes a
line of input, then I continue processing. If it doesn't, then I
select(2) on the descriptor waiting for more data. Once data comes in,
select(2) returns control to the program and I read(2) it onto the
buffer. Repeat, wash. Continue until a newline is found. Normally
this happens on the first read(2) call so the select(2) never gets
called. I've tried just wrapping the read(2) in a loop. It takes the
same amount of time, but it saves kernel time to use select(2). On
FreeBSD, things are amazingly fast. On OS-X, it's like Apple crippled
kqueue(2) and introduced a delay, which sucks, but gives me a chance to
test what a slow memcached server feels like and allowed me to
program/benchmark accordingly.
I could get rid of all of this if there was a binary protocol, however.
:)
On the read(2)'s for the data, I readv(2) the data with blocking
sockets since I don't want to return until I've gotten the data from
the server.
> All of the comments by mc_server_block just say "Switch to non-blocking
> io"...
It also unblocks the connection in the right places. I'm not terribly
happy with what it does to the code, but its effective and doesn't seem
to be slow. 120K get/set's a second. I'm hacking bind-dlz to use
libmemcache and postgresql... working on caching the compiled DNS
responses... bind-dlz only gets 16000 req/sec, but I can squeeze out
over 100K for memcached. :)
> This, in particular, is worrisome:
> {
> [...]
> try_read:
>
> #ifdef HAVE_SELECT
> [...]
> #endif
> rb = read(ms->fd, mc->read_cur, mc->size - (size_t)(mc->cur -
> mc->buf));
> switch(rb) {
> case -1:
> /* We're in non-blocking mode, don't abort because of EAGAIN or
> * EINTR */
> if (errno == EAGAIN || errno == EINTR)
> goto try_read;
> [...]
> }
>
> Won't that just spin if HAVE_SELECT is not defined?
Yup. Doesn't incur any time delay, but its nicer on the kernel to use
SELECT, which is why it's in there by default. I want to add
HAVE_KQUEUE() and HAVE_POLL() then move to those, which is why I added
those bits in the first place.
>> *) Using mmap(2) for buffers instead of malloc(3)'ed memory (not
>> having
>> to copy data from kernel to user and visa versa is always good).
>>
>> *) A binary protocol. The current protocol requires a tad bit of
>> searching, but it's not bad. I don't imagine there would be much of a
>> speedup to be had here, but it's an option.
>>
>> ...but I doubt it'll buy much to go down those routes... no app is
>> going to be limited by libmemcache's performance (that I'm aware of).
>
> (Certainly, profiling would be in order first.)
mc_get_line() takes up most of the time. System calls are the culprit.
Nothing else compares. What algorithms in there are mostly O(1). I
could also cache prevent the malloc(3) that I perform in mc_get(), but
I'm not worried about it. malloc(3) overhead isn't even showing up on
the radar.
I forgot to mention in my previous commit, this last release added the
ability to install your own memory functions via mcSetupMem(). Handy
for writing wrappers such as Ruby or PostgreSQL.
> Random other comment: in your "license", you write: "Use of this
> software in
> programs released under the GPL programs is expressly prohibited by
> the author
> (ie, BSD, closed source, or artistic license is okay, but GPL is not)."
>
> Without initiating a license flamewar, I would like to point out that
> weird licences such as this prevent this from being used by software
> such as LiveJournal itself, which is what memcached was written for in
> the first place.
Yeah, I know it's strange. Most people care about having their code
included in commercial products, I could care less. I care about
making sure my bits stay out of GPL software. I can't use GPL bits
because of their license[1], so why should I return the favor? I just
hate the GPL. If you're not going to use the GPL, then I couldn't give
a shit how you use my software. I'm working on a license with OSI that
covers my interests (version two of the OSSAL license and an
OSSAL-light http://people.FreeBSD.org/~seanc/ossal/) and is easy for
everyone to comply with (BSD + some GPL protection foo). I want to
protect against GPL forks. For now, I'm reserving all rights which
makes things simple. In 99.9% of all instances, ask and I shall grant
usage. I want it used, just not in GPL software unless it's already
established software (ex, I have patches to integrate libmemcache into
perdition...). So, as you can see, I haven't quite figured things out
which is why it's goofy for now. I'll get it squared away by 1.0.
[1] TenDRA, before anyone asks (offlist, however).
--
Sean Chittenden
More information about the memcached
mailing list