Avoiding system calls
October 1, 2012
This is fifth article for the series of "Improving the performance of Warp". Readers are supposed to read the following articles:
- Improving the performance of Warp
- Sending header and body at once
- Caching file descriptors
- Composing HTTP response headers
In this article, I will explain how to avoid the
fcntl() system call
I sometimes compare Mighttpd and nginx with the results of
on Linux and/or
ktrace on BSD variants.
One day, I noticed that nginx uses the
accept4() system call, which I did not know about
at that moment.
(I used to be an expert of BSD variants but am a newbie to Linux.)
In the low level of GHC, file/socket operations are basically implemented as non-blocking.
network package is used,
a listening socket is created with the non-blocking flag set.
When a new connection is accepted from the listening socket,
it is necessary to set the corresponding socket as non-blocking, too.
network package implements this by calling
one is to get the current flags and the other is to set
the flags with the non-blocking flag ORed.
On Linux, the non-block flag of a connected socket
is always unset even if its listening socket is non-blocking.
accept4() system call is an extension version of
accept() on Linux.
It can set the non-blocking flag when accepting.
So, if we use
accept4(), we can avoid two unnecessary
I modified the
network package to use
accept4 on Linux
and it is included in version 188.8.131.52 or later.
On BSD variants
the non-block flag of a connected socket is inherited
from its listening socket.
So, we can also avoid two unnecessary
on BSD variants.
But I have not implemented this.
Date strings are used in various ways. An end HTTP server should return GMT date strings in header fields such as Date:, Last-Modified:, etc:
Date: Mon, 01 Oct 2012 07:38:50 GMT
For logging, a local date string in the Apache style would be convenient:
It is known that
formatTime in the
Data.Time.Format module is too
slow for high performance servers.
Years ago, I implemented faster format packages:
for the former and
unix-time for the latter.
Unfortunately, they are still slow for high performance servers.
And if an HTTP server accepts more than one request per second,
the server repeats the same formatting again and again.
So, formatted date strings should be cached.
The members of the
web-devel mailing-list discussed these issues
with the following assumption:
- Formatting time to date string is a heavy job
- Getting the current time by
gettimeofday()is a light job
The discussion resulted in the following algorithm:
- When a formatted date is required, first issue
getttimeofday(). Then compare it with a cached time.
- If they are equal, return the cached formatted date.
- Otherwise, format the new time to a new formatted date, cache them, and return the new formatted date.
It seems to me that the assumption 2 is not correct.
gettimeofday() was a system call in old Linux
while it is a vsyscall in new Linux.
To my experience, I cannot say that
gettimeofday() is a light job on both old and new Linux.
So, I implemented a new algorithm:
- Designated Haskell thread issues gettimeofday() every second, formats the result time to a date, and caches it.
- When a formatted date is required, the cached formatted date is simply returned.
Only end HTTP servers should return the Date: header field. Since Warp can be used to implement proxies, adding Date: is not warp's job. Also, logging is not warp's job, either. WAI applications should take care of both adding Date: and logging.
Yesod and Mighttpd both use the
fast-logger package for logging.
I modified it so that it provides both algorithms above.
Also, I changed Mighttpd to use the new algorithm
to generate Date:.
The packages with these modification are
already available from Hackage.