3/11/2016 - 9:43 PM

libevent vs libuv

libevent vs libuv

libevent vs libuv

Comparing libevent and libuv. My upfront biased: I want to like libevent. However, I want to objectively compare the two and make an informed decision.

What versions are we comparing?

  • libevent 2.0.22 (Stable) [2014-01-05]
  • libuv 1.8.0 (Stable) [2015-12-15]

Building from source

Both libraries were easy to build.

Building libevent

libevent failed to build correctly the first time. Nothing major, it just couldn't find an OpenSSL header. It was otherwise straightforward to build.

$ ./configure --prefix=$PWD/dist --disable-openssl
$ make && make install

Building libuv

Building libuv was straightforward. Since it was cloned from GitHub I had to run autogen.sh.

$ sh autogen.sh
$ ./configure --prefix=$PWD/dist
$ make && make install

Library size

The most superficial comparison we can make is a size comparison of the compiled static lib. While superficial, library size does matter for certain applications.

  • libevent.a 981K
  • libuv.a 681K

NOTE: this isn't the whole story. libevent_core.a is smaller than libuv.a and may be all that is needed. libevent.a is deprecated and one is supposed to link with libevent_core.a and the link against the optional libraries as needed.

Some more superficial comparisons

Both code bases use a K&Rish style. libevent uses tab for indention while libuv uses 2-spaces. With my editor's tabs defaulting to a width of 8 it makes libevent look more complex due to deep levels of indention, but this is superficial... Setting the width to 2 make the indention levels look about equal. libuv probably has an advantage here as overall the functions are shorter and the levels of depth less.

In libevent not every public function is prefixed with ev some stuff uses event_ while others use bufferevent_, etc. In libuv it seems every public function is prefixed with uv_.

libevent's headers have headerdoc style comments while libuv does not. libuv documentation is separated from the source.

GNU complexity scores

To my surprise libuv scored slightly worse than libevent. The difference is negligible and neither library was riddle with unmaintainable code.

libevent complexity

$ complexity --histogram --score --thresh=7 ~/src/libevent-2.0.22-stable/*.c

libevent complexity results:

Complexity Histogram
Score-Range  Lin-Ct
    0-9        1936 ************************************************************
   10-19       1067 *********************************
   20-29        175 *****
   30-39        136 ****
   40-49        103 ***

Scored procedure ct:       49
Non-comment line ct:     3417
Average line score:        12
25%-ile score:              8 (75% in higher score procs)
50%-ile score:              9 (half in higher score procs)
75%-ile score:             13 (25% in higher score procs)
Highest score:             44 (epoll_apply_one_change() in epoll.c)
Unscored procedures:        1

libevent's most complex function is for some reason pointlessly wrapped in an if block that is always true. This inflates its complexity score. The if block's conditional is if (1) and it wraps everything but the variable declarations and the final return statement.

The logic of the most complex function starts with an ominous warning comment /* The logic here is a little tricky. [sic] */. It is strikingly aware of the needless complexity of the function. Continuing on to another comment /* TODO: Turn this into a switch or a table lookup. */. At least the libevent contributors know where their problem code is and have a plan for it.

Even with the added indention depth the deepest code is 5 levels. It would only be 4 with the if (1) conditional removed. The function comes in a 154 lines.

libuv complexity

$ complexity --histogram --score --thresh=7  ~/src/libuv/src/*.c ~/src/libuv/src/unix/*.c

libuv complexity results:

Complexity Histogram
Score-Range  Lin-Ct
    0-9         804 ****************************************
   10-19       1212 ************************************************************
   20-29        573 ****************************
   30-39          0
   40-49        173 *********

Scored procedure ct:       39
Non-comment line ct:     2762
Average line score:        15
25%-ile score:              9 (75% in higher score procs)
50%-ile score:             12 (half in higher score procs)
75%-ile score:             20 (25% in higher score procs)
Highest score:             49 (uv__io_poll() in src/unix/kqueue.c)
Unscored procedures:        1

libuv's most complex functions starts by declaring 18 local variables and the line count comes in at 227 lines.

The function is deeply nested loops and if conditionals. The deepest level of indention is 7 levels.

While it is tempting to declare this terrible code, I will withhold judgment because I'm unfamiliar with these libraries and their implementations. I truly can't say I can do better, yet...

I used to prefer 2-space indentation, but I'm starting to question that preference. I'm now starting to think it actually makes code more difficult to read and worse it makes bad code look deceptively simple.

Easy of use

To gauge the easy of use I rewrote two sample programs, one for each library. The simplest useful program that can be created with each library is an HTTP echo server. Links to the respective GitHub repos are below:


Memory consumption

Not only memory consumption, but how easy it is to estimate and reason about the memory characteristics of programs written with each respective library.