Socket#sendfile
Request for comments
Benchmark result:
copy 4.0kiB 209.50k ( 4.77µs) (± 3.76%) 0.0B/op 1.23× slower
sendfile 4.0kiB 257.89k ( 3.88µs) (± 3.72%) 0.0B/op fastest
copy 1.0MiB 1.09k (914.69µs) (± 5.40%) 0.0B/op 2.30× slower
sendfile 1.0MiB 2.51k (397.83µs) (± 2.92%) 0.0B/op fastest
copy 128MiB 7.94 (125.88ms) (± 5.99%) 0.0B/op 2.16× slower
sendfile 128MiB 17.17 ( 58.24ms) (± 3.59%) 0.0B/op fastest
Benchmark code:
require "benchmark"
require "socket"
nc = Process.new "nc", ["-l", "-k", "localhost", "1234"]
{ 4096, 1024**2, 128 * 1024**2 }.each do |size|
TCPSocket.open("localhost", 1234) do |s|
File.open("/tmp/a", "w+") do |a|
a.delete
a.print "a" * size
Benchmark.ips do |x|
x.report("copy #{size.humanize_bytes}") do
a.rewind
IO.copy a, s, a.size
end
x.report("sendfile #{size.humanize_bytes}") do
a.rewind
s.sendfile a, a.size
end
end
end
end
end
nc.kill
Obviously have to copy/write the read buffer of the source before the sendfile call, just as in #8919, but waiting to see if that's being accepted first
Please allow this discussion to continue before making any changes to the code
libevent implements sendfile in its event buffer thingy. But I guess there must been some reason to why Crystal has it's own buffering layer instead of using libevent's?
http://www.wangafu.net/~nickm/libevent-2.1/doxygen/html/buffer_8h.html#a601996b1fc1f5c165dc62b89acbd069e
I think those functions were unknown to the implementors.
I think IO::Buffered was implemented before we started using libevent.
I'd prefer not to bind crystal's IO design closer to libevent than it already is, personally.
How is the impact of using sendfile with a large file? Does the system gets into a long syscall? I guess when the fd is set as nonblocking it should return as soon as the output buffer is full, but is worth to try. Just because the test benchmark gives better numbers doesn't necessarily mean is good for a system running with many concurrent users. We need to check how it affects latency in those cases.
In any case, I think I'd prefer to have the Socket#sendfile but not make the automatic switch in IO#copy. If sendfile only works with sockets (and files?) then it makes sense to be a method in the Socket class. And let the one writing the code to decide if the syscall is preferred or not. At least as a non-breaking change starting point.