Sendfile: a system call for web developers to know about
jvns.ca
jvns.ca
https://tn123.org/mod_xsendfile/ https://www.nginx.com/resources/wiki/start/topics/examples/x...
But consider the usecase where you have a bigger download hidden behind some kind of authentication. You'd do that authentication in your normal backend and return the X-Accel-Redirect header in your response. Nginx will then take over and send the data using sendfile.
http://nginx.org/en/docs/http/ngx_http_core_module.html#send...
The simpler sendfile=on would just be instructing nginx to use it for static files.
It is better (more powerful) to have simple primitives, and work up from them.
If you really want something like sendfile, you could also just find the right library.
As for headers/trailers, it's a file descriptor, nothing prevents you from writing to it before or after the sendfile() call.
Simple primitives are fine but this is faster. Unless you are going to do the page flipping tricks that Irix did, you can't approach this speed (and Irix tricks aren't as fast as this, better than the bcopy() but not as fast as this).
One would think that mmap is faster because it does less work but that's not true if you include the open/mmap, read is always fast in that case (which is surprising to me, it used to be that there was a cross over point).
But your whole comment sort of misses the point of sendfile. If you are doing little file transfers, use read/write, they are fine. The overhead of the syscalls matter if the size is small. But sendfile is for big data and there the limit, of perf, as the size goes to infinity, is 2x.
The BSD variant supports headers and trailers, and even if it didn't you're under no obligation to not use other system calls to send data on the socket.
If you're running the kind of workload where sendfile() is valuable you would tend to pre-cache variants that you intended to serve. Consider: why escape the file every time I serve it rather than doing it once?
Even if you did some of these transforms on-the-fly, you can still benefit from sendfile() by caching the transformed version of the file in a tmpfs mount and serving future requests for the same variant from the cached version (with sendfile()).
> If you really want something like sendfile, you could also just find the right library.
Due to the nature of BSD sockets you cannot implement sendfile() in a library. The write() system call (and its vectorized equivalents, along with send(), sendmsg(), etc.) all make a copy of the data to be sent into kernel network buffers[0]. This copy from user mode to kernel mode accounts for the bulk of the computational cost of building a static HTTP server, for example, and can become the bottleneck in terms of maxing out the performance of said server. sendfile() is able to elide this copy by taking advantage of kernel-resident filesystem caches. For any given file the first call to sendfile() will bring the file into memory; assuming a sufficient small corpus (or a sufficiently lucky eviction strategy) the bulk of files served can be served directly from kernel memory.
Also, it's certainly lighter weight than putting the entire HTTP server in the kernel (which, as I understand it, is what Microsoft did at some point for IIS).
[0]: With a sufficiently enlightened memory management system in the kernel one could potentially make these zero-copy for data that was, say, mmap()'d. This would offer the potential for sendfile() like performance from a user-mode library, but would require much more delicate plumbing of memory mapped pages through the networking subsystem.
Just a quick idea, would mmapped files, combined with the aio_write() interface not give us the same functionality as sendfile()?
I suspect that Linux, at least, does not do this. That said, I'm curious about the difficulty of plumbing such an optimization. Unfortunately due to other implementation details of the networking stack I work on day to day it wouldn't have any implications for that, but it might have some for one of my side projects.