Awesome! This looks entirely appropriate for 1.7 and even a 1.6.4 release, since the changes are all "under the hood".
On Fri, Apr 6, 2018 at 6:26 AM, <kot...@apache.org> wrote: > Author: kotkov > Date: Fri Apr 6 11:26:47 2018 > New Revision: 1828509 > > URL: http://svn.apache.org/viewvc?rev=1828509&view=rev > Log: > Win32: Improve apr_file_read() performance on buffered files by reducing > the amount of ReadFile() calls for large reads. > > Previously, reading has been implemented with a loop that keeps filling in > the internal 4KB buffer and copying the data from it. This patch reduces > the amount of syscalls for large reads by performing them with a single > syscall, if possible. With the new approach, reads are first handled from > the buffer — however, once the buffer is empty and the remaining chunk > exceeds the capacity of the internal buffer, it will be read with a single > syscall. > > Otherwise, the behavior is unchanged: we fill in the buffer up to its > capacity and serve the requested part from it. To avoid introducing a > regression in the case when the large read happens with a single syscall, > copy the final part of the data into the internal buffer so that seeking > backwards (within the bufsize) and reading would work from the buffer. > > A quick benchmark shows the significant reduction of the CPU time for > an application that uses buffered files with both small and large reads > (the large reads are approximately 128 KB-sized): > > CPU time: 27.250 s → 17.203 s > Amount of syscalls: 1,579,587 → 139,899 > > > * file_io/win32/readwrite.c > (read_buffered): Reimplement the core part of this function as described > above. Note that the new approach no longer requires a loop, as in the > case when the buffer is empty, we only need to make a single syscall, > either to fill the internal buffer or to read directly into the destination > buffer. Rename a couple of local variables for clarity. > > * test/testfile.c > (test_empty_read_buffered, > test_large_read_buffered, > test_two_large_reads_buffered, > test_small_and_large_reads_buffered, > test_read_buffered_spanning_over_bufsize, > test_single_byte_reads_buffered, > test_read_buffered_seek): New tests. > (testfile): Run the new tests. > > * CHANGES: Add entry. > > Modified: > apr/apr/trunk/CHANGES > apr/apr/trunk/file_io/win32/readwrite.c > apr/apr/trunk/test/testfile.c > > Modified: apr/apr/trunk/CHANGES > URL: > http://svn.apache.org/viewvc/apr/apr/trunk/CHANGES?rev=1828509&r1=1828508&r2=1828509&view=diff > ============================================================================== > --- apr/apr/trunk/CHANGES [utf-8] (original) > +++ apr/apr/trunk/CHANGES [utf-8] Fri Apr 6 11:26:47 2018 > @@ -1,6 +1,9 @@ > -*- coding: utf-8 -*- > Changes for APR 2.0.0 > > + *) apr_file_write: Optimize large reads from buffered files on Windows. > + [Evgeny Kotkov] > + > *) reslist: Add apr_reslist_fifo_set() to allow for reusing resources > in FIFO mode instead of the default LIFO mode. [Yann Ylavic] > > > Modified: apr/apr/trunk/file_io/win32/readwrite.c > URL: > http://svn.apache.org/viewvc/apr/apr/trunk/file_io/win32/readwrite.c?rev=1828509&r1=1828508&r2=1828509&view=diff > ============================================================================== > --- apr/apr/trunk/file_io/win32/readwrite.c (original) > +++ apr/apr/trunk/file_io/win32/readwrite.c Fri Apr 6 11:26:47 2018 > @@ -144,8 +144,9 @@ static apr_status_t read_buffered(apr_fi > { > apr_status_t rv; > char *pos = (char *)buf; > - apr_size_t blocksize; > - apr_size_t size = *len; > + apr_size_t bytes_read; > + apr_size_t size; > + apr_size_t remaining = *len; > > if (thefile->direction == 1) { > rv = apr_file_flush(thefile); > @@ -157,29 +158,62 @@ static apr_status_t read_buffered(apr_fi > thefile->dataRead = 0; > } > > - rv = 0; > - while (rv == 0 && size > 0) { > - if (thefile->bufpos >= thefile->dataRead) { > - apr_size_t read; > - rv = read_with_timeout(thefile, thefile->buffer, > - thefile->bufsize, &read); > - if (read == 0) { > - if (rv == APR_EOF) > - thefile->eof_hit = TRUE; > - break; > - } > - else { > - thefile->dataRead = read; > - thefile->filePtr += thefile->dataRead; > - thefile->bufpos = 0; > - } > + /* Copy the data we have in the buffer. */ > + size = thefile->dataRead - thefile->bufpos; > + if (size > remaining) { > + size = remaining; > + } > + memcpy(pos, thefile->buffer + thefile->bufpos, size); > + pos += size; > + remaining -= size; > + thefile->bufpos += size; > + > + if (remaining == 0) { > + /* Nothing to do more, keep *LEN unchanged and return. */ > + return APR_SUCCESS; > + } > + /* The buffer is empty, but the caller wants more. > + * Decide on the most appropriate way to read from the file: > + */ > + if (remaining > thefile->bufsize) { > + /* If the remaining chunk won't fit into the buffer, read it into > + * the destination buffer with a single syscall. > + */ > + rv = read_with_timeout(thefile, pos, remaining, &bytes_read); > + thefile->filePtr += bytes_read; > + pos += bytes_read; > + /* Also, copy the last BUFSIZE (or less in case of a short read) > bytes > + * from the chunk to our buffer so that seeking backwards and reading > + * would work from the buffer. > + */ > + size = thefile->bufsize; > + if (size > bytes_read) { > + size = bytes_read; > } > + memcpy(thefile->buffer, pos - size, size); > + thefile->bufpos = size; > + thefile->dataRead = size; > + } > + else { > + /* The remaining chunk fits into the buffer. Read up to BUFSIZE > bytes > + * from the file to our internal buffer. > + */ > + rv = read_with_timeout(thefile, thefile->buffer, thefile->bufsize, > &bytes_read); > + thefile->filePtr += bytes_read; > + thefile->bufpos = 0; > + thefile->dataRead = bytes_read; > + /* Copy the required part to the caller. */ > + size = remaining; > + if (size > bytes_read) { > + size = bytes_read; > + } > + memcpy(pos, thefile->buffer, size); > + pos += size; > + thefile->bufpos += size; > + } > > - blocksize = size > thefile->dataRead - thefile->bufpos ? > thefile->dataRead - thefile->bufpos : size; > - memcpy(pos, thefile->buffer + thefile->bufpos, blocksize); > - thefile->bufpos += blocksize; > - pos += blocksize; > - size -= blocksize; > + if (bytes_read == 0 && rv == APR_EOF) { > + thefile->eof_hit = TRUE; > } > > *len = pos - (char *)buf; > > Modified: apr/apr/trunk/test/testfile.c > URL: > http://svn.apache.org/viewvc/apr/apr/trunk/test/testfile.c?rev=1828509&r1=1828508&r2=1828509&view=diff > ============================================================================== > --- apr/apr/trunk/test/testfile.c (original) > +++ apr/apr/trunk/test/testfile.c Fri Apr 6 11:26:47 2018 > @@ -1843,6 +1843,364 @@ static void test_append_read(abts_case * > apr_file_remove(fname, p); > } > > +static void test_empty_read_buffered(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testempty_read_buffered.dat"; > + apr_size_t len; > + apr_size_t bytes_read; > + char buf[64]; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "create empty test file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Test an empty read. */ > + len = 1; > + rv = apr_file_read_full(f, buf, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_large_read_buffered(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testlarge_read_buffered.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char *buf; > + char *buf2; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + len = 80000; > + buf = apr_palloc(p, len); > + memset(buf, 'a', len); > + rv = apr_file_write_full(f, buf, len, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Test a single large read. */ > + buf2 = apr_palloc(p, len); > + rv = apr_file_read_full(f, buf2, len, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, (int)len, (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + /* Test that we receive an EOF. */ > + len = 1; > + rv = apr_file_read_full(f, buf2, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_two_large_reads_buffered(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testtwo_large_reads_buffered.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char *buf; > + char *buf2; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + len = 80000; > + buf = apr_palloc(p, len); > + memset(buf, 'a', len); > + rv = apr_file_write_full(f, buf, len, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Test two consecutive large reads. */ > + buf2 = apr_palloc(p, len); > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, len / 2, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, (int)(len / 2), (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, len / 2, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, (int)(len / 2), (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf + len / 2, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + /* Test that we receive an EOF. */ > + len = 1; > + rv = apr_file_read_full(f, buf2, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_small_and_large_reads_buffered(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testtwo_large_reads_buffered.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char *buf; > + char *buf2; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + len = 80000; > + buf = apr_palloc(p, len); > + memset(buf, 'a', len); > + rv = apr_file_write_full(f, buf, len, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Test small read followed by a large read. */ > + buf2 = apr_palloc(p, len); > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, 5, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, 5, (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, len - 5, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, (int)(len - 5), (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf + 5, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + /* Test that we receive an EOF. */ > + len = 1; > + rv = apr_file_read_full(f, buf2, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_read_buffered_spanning_over_bufsize(abts_case *tc, void > *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testread_buffered_spanning_over_bufsize.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char *buf; > + char *buf2; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + len = APR_BUFFERSIZE + 1; > + buf = apr_palloc(p, len); > + memset(buf, 'a', len); > + rv = apr_file_write_full(f, buf, len, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Test reads than span over the default buffer size. */ > + buf2 = apr_palloc(p, len); > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, APR_BUFFERSIZE - 1, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, APR_BUFFERSIZE - 1, (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + memset(buf2, 0, len); > + rv = apr_file_read_full(f, buf2, 2, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, 2, (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, buf2, bytes_read) == 0); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + /* Test that we receive an EOF. */ > + len = 1; > + rv = apr_file_read_full(f, buf2, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_single_byte_reads_buffered(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testsingle_byte_reads_buffered.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char *buf; > + apr_size_t total; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + len = 40000; > + buf = apr_palloc(p, len); > + memset(buf, 'a', len); > + rv = apr_file_write_full(f, buf, len, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + total = 0; > + while (1) { > + memset(buf, 0, len); > + rv = apr_file_read_full(f, buf, 1, &bytes_read); > + if (rv == APR_EOF) { > + break; > + } > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, 1, (int)bytes_read); > + ABTS_INT_EQUAL(tc, 'a', buf[0]); > + total += bytes_read; > + } > + ABTS_INT_EQUAL(tc, (int)len, (int)total); > + > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > +static void test_read_buffered_seek(abts_case *tc, void *data) > +{ > + apr_status_t rv; > + apr_file_t *f; > + const char *fname = "data/testtest_read_buffered_seek.dat"; > + apr_size_t len; > + apr_size_t bytes_written; > + apr_size_t bytes_read; > + char buf[64]; > + apr_off_t off; > + > + apr_file_remove(fname, p); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_WRITE, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for writing", rv); > + rv = apr_file_write_full(f, "abcdef", 6, &bytes_written); > + APR_ASSERT_SUCCESS(tc, "write to file", rv); > + apr_file_close(f); > + > + rv = apr_file_open(&f, fname, APR_FOPEN_READ | APR_FOPEN_BUFFERED, > + APR_FPROT_OS_DEFAULT, p); > + APR_ASSERT_SUCCESS(tc, "open test file for reading", rv); > + > + /* Read one byte. */ > + memset(buf, 0, sizeof(buf)); > + rv = apr_file_read_full(f, buf, 1, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, 1, (int)bytes_read); > + ABTS_INT_EQUAL(tc, 'a', buf[0]); > + > + /* Seek into the middle of the file. */ > + off = 3; > + rv = apr_file_seek(f, APR_SET, &off); > + APR_ASSERT_SUCCESS(tc, "change file read offset", rv); > + ABTS_INT_EQUAL(tc, 3, (int)off); > + > + /* Read three bytes. */ > + memset(buf, 0, sizeof(buf)); > + rv = apr_file_read_full(f, buf, 3, &bytes_read); > + APR_ASSERT_SUCCESS(tc, "read from file", rv); > + ABTS_INT_EQUAL(tc, 3, (int)bytes_read); > + ABTS_TRUE(tc, memcmp(buf, "def", bytes_read) == 0); > + > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); > + > + /* Test that we receive an EOF. */ > + len = 1; > + rv = apr_file_read_full(f, buf, len, &bytes_read); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + ABTS_INT_EQUAL(tc, 0, (int)bytes_read); > + rv = apr_file_eof(f); > + ABTS_INT_EQUAL(tc, APR_EOF, rv); > + apr_file_close(f); > + > + apr_file_remove(fname, p); > +} > + > abts_suite *testfile(abts_suite *suite) > { > suite = ADD_SUITE(suite) > @@ -1899,6 +2257,13 @@ abts_suite *testfile(abts_suite *suite) > abts_run_test(suite, test_atomic_append, NULL); > abts_run_test(suite, test_append_locked, NULL); > abts_run_test(suite, test_append_read, NULL); > + abts_run_test(suite, test_empty_read_buffered, NULL); > + abts_run_test(suite, test_large_read_buffered, NULL); > + abts_run_test(suite, test_two_large_reads_buffered, NULL); > + abts_run_test(suite, test_small_and_large_reads_buffered, NULL); > + abts_run_test(suite, test_read_buffered_spanning_over_bufsize, NULL); > + abts_run_test(suite, test_single_byte_reads_buffered, NULL); > + abts_run_test(suite, test_read_buffered_seek, NULL); > > return suite; > } > >