NAME

IO::Vectored - Read from or write to multiple buffers at once

WRITE SYNOPSIS

use IO::Vectored;

syswritev($file_handle, "hello", "world") || die "syswritev: $!";

READ SYNOPSIS

use IO::Vectored;

my $buf1 = " " x 5;
my $buf2 = " " x 5;

sysreadv($file_handle, $buf1, $buf2) || die "sysreadv: $!";

## if input is "abcdefg" then:
##  $buf1 eq "abcde"
##  $buf2 eq "fg   "

DESCRIPTION

Vectored-IO is sometimes called "scatter-gather" IO. The idea is that instead of doing multiple read(2) or write(2) system calls for each string, your program creates a vector of pointers to all the strings you wish to read/write and then does a single system call referencing this vector.

Although some people consider these interfaces contrary to the minimalist design principles of unix, vectored-IO has certain advantages which are described below.

This module is an interface to your system's readv(2) and writev(2) vectored-IO system calls specified by POSIX.1. It exports the functions syswritev and sysreadv which are almost the same as the syswrite and sysread perl functions except that they accept multiple strings and always have default length and offset parameters.

ADVANTAGES

The first advantage of vectored-IO is that it reduces the number of system calls required. This provides an atomicity guarantee in that your reads/writes won't be intermingled with the reads/writes of other processes when you aren't expecting it and also eliminates a constant overhead particular to your system.

Another advantage of vectored-IO is that doing multiple system calls can result in excess network packets being sent. The classic example of this is a web-server sending a static file. If the server write()s the HTTP headers and then write()s the file data, the kernel might send the headers and file in separate network packets. Ensuring a single packet is better for latency and bandwidth consumption. TCP_CORK is a solution to this issue but it is Linux-specific and requires more system calls.

Of course an alternative to vectored-IO is to copy the buffers together into a contiguous buffer before calling write(2). The performance trade-off of this is that a potentially large buffer needs to be allocated and then all the smaller buffers copied into it. Also, if your buffers are backed by memory-mapped files (created with File::Map for instance) then this approach results in an unnecessary copy of the data to userspace. If you use vectored-IO then files can be copied directly from the file-system cache into the socket's mbuf.

Note that as with anything the performance benefits of vectored-IO will vary from application to application and you shouldn't retro-fit it onto an application unless benchmarking has shown measurable benefits. However, vectored-IO can sometimes be more programmer-convenient than regular IO and may be worth using for that reason alone.

RETURN VALUES AND ERROR CONDITIONS

As mentioned above, this module's interface tries to match syswrite and sysread so the same caveats that apply to those functions apply to the vectored interfaces. In particular, you should not mix these calls with userspace-buffered interfaces such as print or seek. Mixing the vectored interfaces with syswrite and sysread is fine though.

syswritev returns the number of bytes written (usually the sum of the lengths of all arguments). If it returns less, either there was an error which is indicated in $! or you are using non-blocking IO in which case it is up to you to adjust it so that the next syswritev points to the remaining data.

sysreadv returns the number of bytes read up to the sum of the lengths of all arguments. Note that unlike sysread, sysreadv will not truncate any buffers (see the "READ SYNOPSIS" above and the TODO below).

Both of these functions can also return undef if the underlying readv(2) or writev(2) system calls fail for any reason other than EINTR. When undef is returned, $! will be set with the error.

Like sysread/syswrite, the vectored versions also croak for various reasons such as passing in too many arguments (more than IO::Vectored::IOV_MAX), trying to use a closed file-handle, or trying to write to a read-only/constant string. See the t/exceptions.t test for a full list.

Although not specific to vectored-IO, when accessing mmap()ed memory, a SIGBUS signal can kill your process if another process truncates the backing file while you are accessing it.

SENDFILE

The non-standard sendfile(2) system call can do one less copy than vectored-IO because the file data can be copied directly from the filesystem cache into the final network packet if the hardware and network driver support scatter-gather IO.

Another advantage of sendfile() is that the file pages are never actually mapped into virtual address space. Because the network hardware can gather the data from the OS file-system cache with Direct Memory Access (DMA), there is less pressure on the Translation Lookaside Buffer (TLB). The consequence is less CPU usage per page transferred.

With sendfile(), the number of bytes to send with each system call is specified by size_t so you still have to call it multiple times in order to send files too large to map into virtual memory on 32-bit systems. However, sendfile() doesn't require re-mmaping large files throughout the send like vectored-IO does on 32 bit systems so it can send files in the fewest number of system calls.

A good rule of thumb is that sendfile() is best for large files and vectored-IO is best for small files.

Unfortunately, where operating systems have implemented it at all, the sendfile() interfaces are different. The rest of this section will briefly describe the pros and cons of some implementations.

Linux has the most limited sendfile() implementation. On Linux, a system call is required for each file to be sent, unlike with vectored-IO. Also, a solution such as TCP_CORK is needed to avoid excess network packets. If you are using 32 bit off_t then you will need the sendfile64(2) transitional interface. Note that while this function lets you send large files, you still need to call sendfile() multiple times since the amount you wish to send at once is stored in a size_t.

FreeBSD's sendfile() allows you to specify leading or trailing vectored-IO in addition to the file. This mostly gets rid of the need for TCP_CORK-like solutions. Sending multiple files in one system call is possible but only by taking advantage of one of the vectored-IO parameters in which case you must choose one and only one of the files to get the advantages of sendfile(). FreeBSD's off_t is always 64 bits so there is no need for a sendfile64().

As well as a Linux-like sendfile(), Solaris has a fully vectorised interface called sendfilev() which allows the arbitrary mixing of files and in-process memory buffers. Although in many ways this is the best of both worlds, it still doesn't guarantee atomicity like standard vectored-IO does. Note that Solaris also provides sendfile64() and sendfilev64() interfaces because off_t can be 32 or 64 bits so an explicitly 64-bit transitional interface is required.

As if all the above caveats weren't enough, many sendfile() implementations will only work when sending from a file (obviously) and to a network socket (less obviously). So in order for your code to be fully general and portable you may have to implement one code path that uses sendfile() and one that doesn't.

How are those minimalist design principles of unix sounding now? :)

TODO

Consider truncating input strings like sysread does. Please don't depend on the non-truncating behaviour of sysreadv until version 1.000.

Implement some helper utilities to make using non-blocking IO easier such as a utility to shift off N bytes from the beginning of a vector.

To the extent possible, make it do the right thing for file-handles with non-raw encodings and unicode strings. Any test-cases are appreciated.

Investigate if there is a performance benefit in eliminating the perl subs and re-implementing their logic in XS.

Think about whether this module should support vectors larger than IOV_MAX by calling writev/readv multiple times. This should be opt-in because it breaks the atomicity guarantee.

Support windows with ReadFileScatter, WriteFileGather, WSASend, and WSARecv.

SEE ALSO

IO-Vectored github repo

Useful modules to combine with vectored-IO:

File::Map / Sys::Mmap

overload::substr

String::Slice

Even though sendfile() is a solution to a somewhat different problem (see the SENDFILE section above), here are some perl interfaces:

Sys::Sendfile / Sys::Syscall / Sys::Sendfile::FreeBSD

AUTHOR

Doug Hoyte, <doug@hcsw.org>

COPYRIGHT & LICENSE

Copyright 2013-2014 Doug Hoyte.

This module is licensed under the same terms as perl itself.