Skip to content

Commit bf75865

Browse files
committed
main/streams/streams: optimized code path for unfiltered regular files
If we know the exact file size already, we can allocate the right buffer size and issue only one read() system call. This eliminates unnecessary buffer allocations (file size plus 8 kB), eliminates buffer reallocations via zend_string_truncate(), and eliminates the last read() system call. Benchmark: echo Hello World >/tmp/hello.txt <?php for ($i = 1; $i <= 1000000; $i++) file_get_contents('/tmp/hello.txt'); Previously: openat(AT_FDCWD, "/tmp/hello.txt", O_RDONLY) = 3 newfstatat(3, "", {st_mode=S_IFREG|0600, st_size=12, ...}, AT_EMPTY_PATH) = 0 lseek(3, 0, SEEK_CUR) = 0 newfstatat(3, "", {st_mode=S_IFREG|0600, st_size=12, ...}, AT_EMPTY_PATH) = 0 read(3, "Hello World\n", 8204) = 12 read(3, "", 8192) = 0 close(3) = 0 4,036.55 msec task-clock:u # 0.995 CPUs utilized 4,683,289,286 cycles:u # 1.160 GHz 7,954,269,313 instructions:u # 1.70 insn per cycle 1,165,461,174 branches:u # 288.727 M/sec 1,431,759 branch-misses:u # 0.12% of all branches Optimized: openat(AT_FDCWD, "/tmp/hello.txt", O_RDONLY) = 3 newfstatat(3, "", {st_mode=S_IFREG|0600, st_size=12, ...}, AT_EMPTY_PATH) = 0 lseek(3, 0, SEEK_CUR) = 0 newfstatat(3, "", {st_mode=S_IFREG|0600, st_size=12, ...}, AT_EMPTY_PATH) = 0 read(3, "Hello World\n", 12) = 12 close(3) = 0 3,473.51 msec task-clock:u # 0.997 CPUs utilized 4,132,407,666 cycles:u # 1.190 GHz 6,841,273,246 instructions:u # 1.66 insn per cycle 1,024,464,752 branches:u # 294.936 M/sec 1,478,403 branch-misses:u # 0.14% of all branches For this micro-benchmark, the improvement is 14%. This patch needs to adjust the unit test "bug54946.phpt" because the EBADF warning is no longer logged.
1 parent a3df278 commit bf75865

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

ext/standard/tests/streams/bug54946.phpt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ fclose($stream);
3131
unlink($filename);
3232
?>
3333
--EXPECTF--
34-
Notice: stream_get_contents(): Read of 8192 bytes failed with errno=9 Bad file descriptor in %s on line %d
3534
string(0) ""
36-
37-
Notice: stream_get_contents(): Read of 8192 bytes failed with errno=9 Bad file descriptor in %s on line %d
3835
string(0) ""
39-
40-
Notice: stream_get_contents(): Read of 8192 bytes failed with errno=9 Bad file descriptor in %s on line %d
4136
string(0) ""

main/streams/streams.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,26 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int
14741474
return ZSTR_EMPTY_ALLOC();
14751475
}
14761476

1477+
if (php_stream_is(src, PHP_STREAM_IS_STDIO) &&
1478+
src->readfilters.head == NULL &&
1479+
php_stream_stat(src, &ssbuf) == 0 &&
1480+
#ifdef S_ISREG
1481+
S_ISREG(ssbuf.sb.st_mode) &&
1482+
#endif
1483+
ssbuf.sb.st_size >= 0) {
1484+
/* optimized code path for unfiltered regular files:
1485+
* if we know the exact size, we can allocate the
1486+
* right buffer size and issue only one read() system
1487+
* call */
1488+
1489+
if (ssbuf.sb.st_size <= src->position)
1490+
return ZSTR_EMPTY_ALLOC();
1491+
1492+
const size_t remaining = ssbuf.sb.st_size - src->position;
1493+
if (remaining < maxlen)
1494+
maxlen = remaining;
1495+
}
1496+
14771497
if (maxlen == PHP_STREAM_COPY_ALL) {
14781498
maxlen = 0;
14791499
}

0 commit comments

Comments
 (0)