了解 Linux 写入性能

Understanding Linux write performance

我一直在做一些基准测试,试图了解 Linux 上的写入性能,但我不明白我得到的结果(我在 Ubuntu 17.04 上使用 ext4,不过与比较文件系统相比,我更感兴趣的是理解 ext4。

具体来说,我了解到某些 databases/filesystems 的工作方式是保留数据的陈旧副本,然后将更新写入修改日志。定期地,日志会在陈旧数据上重播以获得新版本的数据,然后将其持久化。如果附加到文件比覆盖整个文件更快,这对我来说才有意义(否则为什么要将更新写入日志?为什么不直接覆盖磁盘上的数据?)。我很好奇追加比覆盖快多少,所以我用 go (https://gist.github.com/msteffen/08267045be42eb40900758c419c3bd38) 写了一个小基准并得到了这些结果:

$ go test ./write_test.go  -bench='.*'
BenchmarkWrite/Write_10_Bytes_10_times-8                30    46189788 ns/op
BenchmarkWrite/Write_100_Bytes_10_times-8               30    46477540 ns/op
BenchmarkWrite/Write_1000_Bytes_10_times-8              30    46214996 ns/op
BenchmarkWrite/Write_10_Bytes_100_times-8                3   458081572 ns/op
BenchmarkWrite/Write_100_Bytes_100_times-8               3   678916489 ns/op
BenchmarkWrite/Write_1000_Bytes_100_times-8              3   448888734 ns/op
BenchmarkWrite/Write_10_Bytes_1000_times-8               1  4579554906 ns/op
BenchmarkWrite/Write_100_Bytes_1000_times-8              1  4436367852 ns/op
BenchmarkWrite/Write_1000_Bytes_1000_times-8             1  4515641735 ns/op
BenchmarkAppend/Append_10_Bytes_10_times-8              30    43790244 ns/op
BenchmarkAppend/Append_100_Bytes_10_times-8             30    44581063 ns/op
BenchmarkAppend/Append_1000_Bytes_10_times-8            30    46399849 ns/op
BenchmarkAppend/Append_10_Bytes_100_times-8              3   452417883 ns/op
BenchmarkAppend/Append_100_Bytes_100_times-8             3   458258083 ns/op
BenchmarkAppend/Append_1000_Bytes_100_times-8            3   452616573 ns/op
BenchmarkAppend/Append_10_Bytes_1000_times-8             1  4504030390 ns/op
BenchmarkAppend/Append_100_Bytes_1000_times-8            1  4591249445 ns/op
BenchmarkAppend/Append_1000_Bytes_1000_times-8           1  4522205630 ns/op
PASS
ok    command-line-arguments  52.681s

这给我留下了两个我想不出答案的问题:

1) 当我从 100 次写入到 1000 次写入时,为什么每个操作的时间增加了这么多? (我知道 Go 会为我重复基准测试,所以我自己进行多次写入可能很愚蠢,但由于我得到了一个奇怪的答案,我想了解为什么) 这是由于 Go 测试中的一个错误(现已修复)

2) 为什么追加到文件不比写入文件快?我认为更新日志的重点是利用追加的相对速度? (请注意,当前的工作台会在每次写入后调用 Sync(),但即使我不这样做,追加也不比写入快,尽管两者总体上都快得多)

如有高手能赐教,不胜感激!谢谢!

关于 (1),我认为这个问题与您的基准测试没有按照 Go 工具期望它们做的事情有关。

来自文档 (https://golang.org/pkg/testing/#hdr-Benchmarks):

The benchmark function must run the target code b.N times. During benchmark execution, b.N is adjusted until the benchmark function lasts long enough to be timed reliably.

我没有看到你的代码使用 b.N,所以虽然基准工具 认为 你 运行 代码 b.N 次,您正在自己管理重复。根据工具实际为 b.N 使用的值,结果会出乎意料地不同。

你实际上可以做 10、100 和 1,000 次,但在所有情况下都要做 b.N 次(使 b.N * 10b.N * 100 等),以便报告基准已适当调整。

关于(2),当一些系统宁愿使用顺序日志来存储操作以重播它们时,这并不是因为附加到文件比覆盖单个文件更快。

在数据库系统中,如果您需要更新特定的记录,您必须首先找到您需要更新的实际文件(以及文件中的位置)。

这可能需要多次索引查找,更新记录后,您可能需要更新这些索引以反映新值。

所以正确的比较是附加到单个日志与进行多次读取然后多次写入。