For few fds, this backend is a bit little slower than poll and select, but it scales phenomenally better. While poll and select usually scale like O(total_fds) where n is the total number of fds (or the highest fd), epoll scales either O(1) or O(active_fds).
The epoll mechanism deserves honorable mention as the most misdesigned of the more advanced event mechanisms: mere annoyances include silently dropping file descriptors, requiring a system call per change per file descriptor (and unnecessary guessing of parameters), problems with dup, returning before the timeout value, resulting in additional iterations (and only giving 5ms accuracy while select on the same platform gives 0.1ms) and so on. The biggest issue is fork races, however — if a program forks then both parent and child process have to recreate the epoll set, which can take considerable time (one syscall per file descriptor) and is of course hard to detect.
Epoll is also notoriously buggy — embedding epoll fds should work, but of course doesn’t, and epoll just loves to report events for totally different file descriptors (even already closed ones, so one cannot even remove them from the set) than registered in the set (especially on SMP systems). Libev tries to counter these spurious notifications by employing an additional generation counter and comparing that against the events to filter out spurious ones, recreating the set when required. Last not least, it also refuses to work with some file descriptors which work perfectly fine with select (files, many character devices…).
Epoll is truly the train wreck analog among event poll mechanisms.
While nominally embeddable in other event loops, this feature is broken in all kernel versions tested so far.
Kqueue deserves special mention, as at the time of this writing, it was broken on all BSDs except NetBSD (usually it doesn’t work reliably with anything but sockets and pipes, except on Darwin, where of course it’s completely useless). Unlike epoll, however, whose brokenness is by design, these kqueue bugs can (and eventually will) be fixed without API changes to existing programs. For this reason it’s not being «auto-detected» unless you explicitly specify it in the flags (i.e. using EVBACKEND_KQUEUE) or libev was compiled on a known-to-be-good (-enough) system like NetBSD.
You still can embed kqueue into a normal poll or select backend and use it only for sockets (after having made sure that sockets work with kqueue on the target platform).
It scales in the same way as the epoll backend, but the interface to the kernel is more efficient (which says nothing about its actual speed, of course). While stopping, setting and starting an I/O watcher does never cause an extra system call as with EPOLL, it still adds up to two event changes per incident. Support for fork () is very bad (but sane, unlike epoll) and it drops fds silently in similarly hard-to-detect cases