1 To: rsync@lists.samba.org
2 Subject: Rsync performance increase through buffering
3 From: Craig Barratt <craig@atheros.com>
4 Message-ID: <auto-000002369223@atheros.com>
5 Date: Sun, 08 Dec 2002 23:48:57 -0800
7 I've been studying the read and write buffering in rsync and it turns
8 out most I/O is done just a couple of bytes at a time. This means there
9 are lots of system calls, and also most network traffic comprises lots
10 of small packets. The behavior is most extreme when sending/receiving
11 file deltas of identical files.
13 The main case where I/O is buffered is writes from the server (when
14 io multiplexing is on). These are usually buffered in 4092 byte
15 chunks with a 4 byte header. However, reading of these packets is
16 usually unbuffered, and writes from the client are generally not
17 buffered. For example: when receiving 1st phase checksums (6 bytes
18 per block), 2 reads are done: one of 4 bytes and one of 2 bytes,
19 meaning there are 4 system calls (select/read/select/read) per 6
20 bytes of checksum data).
22 One cost of this is some performance, but a significant issue is that
23 unbuffered writes generate very short (and very many) ethernet packets,
24 which means the overhead is quite large on slow network connections.
26 The initial file_list writing is typically buffered, but reading it on
29 There are some other unneeded system calls:
31 - One example is that show_progress() calls gettimeofday() even
32 if do_progress is not set. show_progress() is called on every
33 block, so there is an extra system call per (700 byte) block.
35 - Another example is that file_write writes each matching (700 byte)
36 block without buffering, so that's another system call per block.
38 To study this behavior I used rsync-2.5.6cvs and had a benchmark area
39 comprising around 7800 files of total size 530MB.
41 Here are some results doing sends and receives via rsyncd, all on the
42 same machine, with identical source and destination files. In each
43 case --ignore-times (-I) is set, so that every file is processed:
47 strace -f rsync -Ir . localhost::test |& wc
49 shows there are about 2,488,775 system calls.
53 strace -f rsync -Ir localhost::test . |& wc
55 shows there are about 1,615,931 system calls.
57 - Rsyncd has a roughly similar numbers of system calls.
59 - Send test from another machine (cygwin/WinXP laptop):
61 tcpdump port 873 |& wc
63 shows there are about 701,111 ethernet packets (many of them only
64 have a 4 byte payload).
66 Since the source and dest files are the same, the send test only
67 wrote 1,738,797 bytes and read 2,139,848 bytes.
69 These results are similar to rsync 2.5.5.
71 Below is a patch to a few files that adds read and write buffering in
72 the places where the I/O was unbuffered, adds buffering to write_file()
73 and removes the unneeded gettimeofday() system call in show_progress().
75 The results with the patch are:
77 - Send test: 46,835 system calls, versus 2,488,775.
79 - Receive test: 138,367 system calls, versus 1,615,931.
81 - Send test from another machine: 5,255 ethernet packets, versus 701,111.
82 If the tcp/ip/udp/802.3 per-packet overhead is around 60 bytes, that
83 means the base case transfers an extra 42MB of data, even though the
84 useful data is only around 2MB.
86 The absolute running time on the local rsyncd test isn't much different,
87 probably because the test is really disk io limited and system calls on
88 an unloaded linux system are pretty fast.
90 However, on a network test doing a send from cygwin/WinXP to rsyncd
91 on rh-linux the running time improves from about 700 seconds to 215
92 seconds (with a cpu load of around 17% versus 58%, if you believe
93 cygwin's cpu stats). This is probably an extreme case since the system
94 call penalty in cygwin is high. But I would suspect a significant
95 improvement is possible with a slow network connection, since a lot
96 less data is being sent.
98 Note also that without -I rsync is already very fast, since it skips
99 (most) files based on attributes.
101 With or without this patch the test suite passes except for
102 daemon-gzip-upload. One risk of buffering is the potential for
103 a bug caused by a missing io_flush: deadlock is possible, so try
104 the patch at your own risk...
108 ###########################################################################
109 diff -bur rsync/fileio.c rsync-craig/fileio.c
110 --- rsync/fileio.c Fri Jan 25 15:07:34 2002
111 +++ rsync-craig/fileio.c Sat Dec 7 22:21:10 2002
116 - return write(f,buf,len);
117 + static char *writeBuf;
118 + static size_t writeBufSize;
119 + static size_t writeBufCnt;
122 + writeBufSize = MAX_MAP_SIZE;
124 + writeBuf = (char*)malloc(MAX_MAP_SIZE);
125 + if (!writeBuf) out_of_memory("write_file");
129 + if ( buf && writeBufCnt < writeBufSize ) {
130 + size_t copyLen = len;
131 + if ( copyLen > writeBufSize - writeBufCnt ) {
132 + copyLen = writeBufSize - writeBufCnt;
134 + memcpy(writeBuf + writeBufCnt, buf, copyLen);
135 + writeBufCnt += copyLen;
139 + if ( !buf || writeBufCnt == writeBufSize ) {
140 + int thisRet = write(f, writeBuf, writeBufCnt);
141 + if ( thisRet < 0 ) return thisRet;
144 + } while ( buf && len > 0 );
149 diff -bur rsync/flist.c rsync-craig/flist.c
150 --- rsync/flist.c Sat Jul 27 11:01:21 2002
151 +++ rsync-craig/flist.c Sun Dec 8 16:28:14 2002
156 - io_start_buffering(f);
157 + io_start_buffering_out(f);
160 for (i = 0; i < argc; i++) {
161 diff -bur rsync/io.c rsync-craig/io.c
162 --- rsync/io.c Wed Apr 10 19:11:50 2002
163 +++ rsync-craig/io.c Sun Dec 8 17:54:23 2002
166 static int io_multiplexing_out;
167 static int io_multiplexing_in;
168 -static int multiplex_in_fd;
169 -static int multiplex_out_fd;
170 +static int multiplex_in_fd = -1;
171 +static int multiplex_out_fd = -1;
172 static time_t last_io;
175 @@ -286,17 +286,31 @@
176 static size_t remaining;
179 + static char *buffer;
180 + static size_t bufferIdx = 0;
181 + static size_t bufferSz;
183 - if (!io_multiplexing_in || fd != multiplex_in_fd)
184 + if (fd != multiplex_in_fd)
185 return read_timeout(fd, buf, len);
187 + if (!io_multiplexing_in && remaining == 0) {
189 + bufferSz = 2 * IO_BUFFER_SIZE;
190 + buffer = malloc(bufferSz);
191 + if (!buffer) out_of_memory("read_unbuffered");
193 + remaining = read_timeout(fd, buffer, bufferSz);
199 len = MIN(len, remaining);
200 - read_loop(fd, buf, len);
201 + memcpy(buf, buffer + bufferIdx, len);
209 read_loop(fd, line, 4);
211 remaining = tag & 0xFFFFFF;
214 - if (tag == MPLEX_BASE)
215 + if (tag == MPLEX_BASE) {
216 + if (!buffer || remaining > bufferSz) {
217 + buffer = Realloc(buffer, remaining);
218 + if (!buffer) out_of_memory("read_unbuffered");
219 + bufferSz = remaining;
221 + read_loop(fd, buffer, remaining);
229 rprintf((enum logcode) tag, "%s", line);
233 + if (remaining == 0) {
245 ret = read_unbuffered (fd, buffer + total, N-total);
249 static char *io_buffer;
250 static int io_buffer_count;
252 -void io_start_buffering(int fd)
253 +void io_start_buffering_out(int fd)
255 if (io_buffer) return;
256 multiplex_out_fd = fd;
261 +void io_start_buffering_in(int fd)
263 + multiplex_in_fd = fd;
267 * Write an message to a multiplexed stream. If this fails then rsync
271 multiplex_out_fd = fd;
273 - io_start_buffering(fd);
274 + io_start_buffering_out(fd);
275 io_multiplexing_out = 1;
278 diff -bur rsync/main.c rsync-craig/main.c
279 --- rsync/main.c Thu Aug 1 13:46:59 2002
280 +++ rsync-craig/main.c Sun Dec 8 17:39:07 2002
285 + io_start_buffering_in(f_in);
286 + io_start_buffering_out(f_out);
287 send_files(flist,f_out,f_in);
291 close(error_pipe[1]);
292 if (f_in != f_out) close(f_in);
294 - io_start_buffering(f_out);
295 + io_start_buffering_out(f_out);
297 io_set_error_fd(error_pipe[0]);
303 + io_start_buffering_in(f_in);
304 if (delete_mode && !delete_excluded)
305 recv_exclude_list(f_in);
308 extern int cvs_exclude;
309 extern int delete_mode;
310 extern int delete_excluded;
311 + io_start_buffering_out(f_out);
314 if (delete_mode && !delete_excluded)
317 rprintf(FINFO,"file list sent\n");
320 + io_start_buffering_out(f_out);
321 send_files(flist,f_out,f_in);
323 if (remote_version >= 24) {
324 /* final goodbye message */
327 wait_process(pid, &status);
331 exit_cleanup(status);
334 diff -bur rsync/progress.c rsync-craig/progress.c
335 --- rsync/progress.c Sun Apr 7 22:28:57 2002
336 +++ rsync-craig/progress.c Sat Dec 7 18:57:19 2002
338 extern int do_progress, am_server;
341 + if (!do_progress) return;
343 gettimeofday(&now, NULL);
345 if (!start_time.tv_sec && !start_time.tv_usec) {
346 diff -bur rsync/proto.h rsync-craig/proto.h
347 --- rsync/proto.h Wed Jul 31 17:37:02 2002
348 +++ rsync-craig/proto.h Sun Dec 8 16:27:55 2002
350 void read_buf(int f,char *buf,size_t len);
351 void read_sbuf(int f,char *buf,size_t len);
352 unsigned char read_byte(int f);
353 -void io_start_buffering(int fd);
354 +void io_start_buffering_out(int fd);
355 +void io_start_buffering_in(int fd);
357 void io_end_buffering(void);
358 void write_int(int f,int32 x);
359 diff -bur rsync/receiver.c rsync-craig/receiver.c
360 --- rsync/receiver.c Tue May 28 08:42:51 2002
361 +++ rsync-craig/receiver.c Sat Dec 7 22:09:04 2002
369 + write_file(fd, NULL, 0);
371 end_progress(total_size);
373 if (fd != -1 && offset > 0 && sparse_end(fd) != 0) {