php / php/php-src

Increase FILLUNIT (currently 5kB) in main/rfc1867.c otherwise large uploads are inefficients and slow

未关闭
#13,189 1 条评论 2 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

Feature Status: Needs Triage
主要语言
C
星标
40.4k
派生
8.1k
平均合并
2 天 13 小时
30 天内合并 PR
96

描述

Description

I recently stumbled upon a very simple file upload code which was quite slow. It turned out that it was IO-limited while writing the temporary file to the disk. A strace showed that PHP would spoonfeed the incoming network data to the disk :

16:51:04 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4142) = 4142
16:51:04 read(6, "\1\5\0\1 \0\0\0", 8)  = 8
16:51:04 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 977) = 977
16:51:04 write(8, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 5119) = 5119
16:51:04 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 5119) = 5119
16:51:04 write(8, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 5119) = 5119
16:51:04 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2096) = 2096
16:51:04 read(6, "\1\5\0\1 \0\0\0", 8)  = 8
16:51:04 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 3023) = 3023
16:51:04 write(8, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 5119) = 5119

On the network/read side this is not very efficient, it uses a lot of syscalls (especially since there is a recurring 8-byte read() call). On my experiment (a n1-standard-8 GCE VM), a single process would use 50% CPU (on the system side, not the user one) with an upload proceeding at 15 MB/s.

On the disk/write side this is also inefficient, and runs quickly in real cloud limits : in my case a 100G "SSD" disk which GCE advertises as 48 MB/s and 3000 IO/s max; my benchmarks with fio confirmed those figures.

The problem with such a small FILLUNIT=5kB buffer, is that you can't get a better throughput than 3000*5 kB/s =15 MB/s. So you don't get the max througput of your disk, you top on IOPS first.

Of course that is amplified by the fact that is a VM and uses - as recommended by Google - the 'noop' IO scheduler and there is no write coalescing. I guess an IO scheduler could mitigate this on a bare-metal local storage. But I think running PHP on such a cloud VM (8 vCPUs/ 32GB RAM/ 100G "SSD") is very common. And the performance drop (and global pressure on the server storage) is very noticeable.

According to https://github.com/php/php-src/commit/44b68122c2ba0cf07cd837af160b3bd01527d081 FILLUNIT has been 5 kB for 23 years.

I would suggest it is time to bump it up. I would suggest at least 64 kB, GCE suggests 256 kB (https://cloud.google.com/compute/docs/disks/optimizing-pd-performance). Only one instance of this buffer is used at once, during the file upload loop (thus per-process), and can be released as soon as the loop is done.

Note that the value is used to allocate a second transient buffer on the stack in multipart_buffer_read_body() - and that might abuse the stack (which defaults to 1MB on Windows and 8MB on Linux if I'm not mistaken).

PHP Version

PHP 8.1.27

Operating System

Debian 10

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 main/rfc1867.c 开始,定位 FILLUNIT 和 multipart_buffer_read_body(),然后检查上传期间缓冲区大小是如何分配的。将报告中的 strace 和 fio 测量结果作为性能背景,并考虑临时栈缓冲区。当一个达成共识的更大值能够提高上传吞吐量,同时不影响上传行为或栈使用情况时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
c
领域
backend, performance
Issue 类型
功能
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。