原始 IO 性能

2007年2月3日

在 lighttpd 1.5.0 中,我们支持多种网络后端。它们的作用是从磁盘获取静态数据并发送给客户端。

我们希望比较不同后端的性能以及何时使用哪种后端。

  • writev
  • linux-sendfile
  • gthread-aio
  • posix-aio
  • linux-aio-sendfile

stat 线程的影响也应进行检查。

我们使用了一个最小配置文件

server.document-root     = "/home/jan/wwwroot/servers/grisu.home.kneschke.de/pages/"

server.port              = 1025
server.errorlog          = "/home/jan/wwwroot/logs/lighttpd.error.log"

server.network-backend   = "linux-aio-sendfile"
server.event-handler     = "linux-sysepoll"
server.use-noatime       = "enable"

server.max-stat-threads  = 2
server.max-read-threads = 64

iostat、vmstat 和 http_load

我们使用 iostatvmstat 来查看系统如何处理负载。

$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
 0  0 506300  12900  20620 437968    0    0 14204    17 5492  3323  4 23  9 63
 0  1 506300  11212  20620 439888    0    0 17720     4 6713  3966  3 29  3 66
 0  1 506300  11664  20632 440356    0    0 14460     8 5416  3120  2 24  2 71
 1  0 506300  18916  20612 433168    0    0 13180    50 5505  3088  2 23  2 72
 0  1 506300  11960  20628 440188    0    0 15860     6 5485  3307  2 24  3 71

$ iostat -xm 5
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
       2.20    0.00   24.40   70.40    0.00    3.00

Device:    rrqm/s wrqm/s    r/s   w/s  rsec/s  wsec/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await  svctm  %util
sda         67.40   0.40  68.00  0.80 13600.00   14.40     6.64     0.01   197.88    12.87  176.83  11.23  77.28
sdb         82.80   0.40  84.60  0.80 17000.00   14.40     8.30     0.01   199.23    23.18  280.16  11.61  99.12
md0          0.00   0.00 302.20  0.80 30520.00    6.40    14.90     0.00   100.75     0.00    0.00   0.00   0.00

我们的 http_load 进程返回了

>http_load -verbose -timeout 40 -parallel 100 -seconds 60 urls.100k
9117 fetches, 100 max parallel, 9.33581e+008 bytes, in 60 seconds
102400 mean bytes/connection
151.95 fetches/sec, 1.55597e+007 bytes/sec
msecs/connect: 5.47226 mean, 31.25 max, 0 min
msecs/first-response: 144.433 mean, 3156.25 max, 0 min
HTTP response codes:
  code 200 -- 9117

我们将使用相同的基准测试和配置来比较不同的后端。

比较

作为比较,我尝试将其他网络服务器也加入到对比中。像往常一样,基准测试结果需要持保留态度。不要完全相信它们,请尝试自行重复测试。

  • lighttpd 1.5.0-svn,配置如上所示
  • litespeed 3.0rc2
    • epoll 和 sendfile 已启用。所有其他选项均为默认值。
  • 来自 OpenSUSE 10.2 软件包的 Apache 2.2.4 event-mpm
    • MinSpareThreads = 25
    • MaxSpareThreads = 75
    • ThreadLimit = 64
    • ThreadsPerChild = 25

我将尝试提供一个精简的、基于文本的配置文件,其中只包含他人重复测试所需的必要选项。

预期

基准测试旨在表明异步文件 I/O 对于单线程网络服务器而言性能良好。我们预期

  • 阻塞式网络后端速度较慢
  • Apache 2.2.4 提供最佳性能,因为它是多线程 + 事件驱动的
  • lighttpd + 异步文件 I/O 的性能可达到 Apache2 的水平

阻塞式文件 I/O 的问题在于,单线程服务器在等待系统调用完成时无法执行其他操作。

基准测试

对不同的后端运行 http_load 显示了异步 I/O 与同步 I/O 的影响。

100k 文件

lighttpd
后端吞吐量请求/秒
writev6.11MByte/s59.77
linux-sendfile6.50MByte/s63.62
posix-aio12.88MByte/s125.75
gthread-aio15.04MByte/s147.08
linux-aio-sendfile15.56MByte/s151.95
其他
litespeed 3.0rc2 (writev)4.35MByte/s42.78
litespeed 3.0rc2 (sendfile)5.49MByte/s53.68
apache 2.2.415.04MByte/s146.93

对于小文件,您可以获得大约 140% 的额外吞吐量。

不使用 no-atime

为了显示 server.use-noatime = "enable" 的影响,我们比较了 gthread-aio 输出在启用和禁用 noatimevmstat 的输出。

启用 O_NOATIME

procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
 1 62 506300   9192  20500 470064    0    0 12426     5 7005  6324  3 27  7 63
 0 63 506300  10188  19768 469732    0    0 14154     2 8252  7614  3 30  0 67
 0 64 506300  10488  19124 470492    0    0 13589     0 8261  7483  3 27  0 69
 0 64 506300  10196  17952 473092    0    0 13062     8 7388  6560  3 25  8 65
 0 64 506300  10656  16836 474720    0    0 11790     0 6378  5074  2 23 11 64

禁用 O_NOATIME

procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
 0 21 506300  10408  15452 491680    0    0 10515   326 5362  1619  2 22 19 57
 3  7 506300  11116  17888 487588    0    0 11020   493 6056  2400  7 25 10 58
 0  0 506300  10200  19704 488004    0    0  8840   365 4506  1622  2 20 29 49
 2 14 506300  10460  21624 485288    0    0 12422   428 6464  1986  2 26 11 60
 0  1 506300   9436  24116 485316    0    0 12640   513 7159  2109  3 28  2 67
 0 21 506300  11864  25768 481588    0    0  8760     5 4436  1571  2 19 39 41
 0 21 506300  10352  24892 483412    0    0 11941   339 6005  1913  3 24 12 6

你会看到 bo (块输出) 上升,而 bi (块输入) 以同样的方式下降。由于你通常不需要 atime (文件访问时间),你应该将文件系统挂载为 noatime, nodiratime 或者使用设置 server.use-noatime = "enable"。默认情况下,此设置是禁用的,以实现向后兼容。

10MByte 文件

lighttpd
后端吞吐量请求/秒磁盘利用率%用户 + 系统
writev17.59MByte/s2.3550 %25 %
linux-sendfile33.13MByte/s3.7770 %30 %
posix-aio50.61MByte/s5.6998%60%
gthread-aio47.97MByte/s5.51100%50%
linux-aio-sendfile44.15MByte/s4.9590%40 %
其他
litespeed 3.0rc2 (sendfile)22.18MByte/s2.7265%35 %
Apache 2.2.442.81MByte/s4.7395%40 %

对于更大的文件,异步 I/O 带来的性能提升仍在大约 50% 左右。

stat() 线程

对于小文件,性能很大程度上受到 stat() 系统调用的影响。在文件被 open() 打开以供读取之前,会首先检查文件是否存在、是否是常规文件以及我们是否可以读取它。这个系统调用本身不是异步的。

我们将使用 gthread-aio 后端和 10 万个文件,再次运行基准测试,这次将 server.max-stat-threads 从 0 更改为 16。

线程吞吐量
08.55MByte/s
113.60MByte/s
214.18MByte/s
412.33MByte/s
812.62MByte/s
1213.10MByte/s
1612.71MByte/s

为了获得最佳性能,您应该将 stat 线程的数量设置为与您的磁盘数量相同。

读取线程

您还可以调整读取线程的数量。每个磁盘读取请求都会被排队,然后由一个读取器池并行执行。目标是使磁盘利用率达到 100%,并在 lighttpd 将数据发送到网络的时间内隐藏 stat()read() 的寻道时间。

线程吞吐量
16.83MByte/s
211.61MByte/s
413.02MByte/s
813.61MByte/s
1613.81MByte/s
3214.04MByte/s
6414.87MByte/s

看起来每个磁盘 2 次读取已经是一个不错的值了。