了解 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 * 10
、b.N * 100
等),以便报告基准已适当调整。
关于(2),当一些系统宁愿使用顺序日志来存储操作以重播它们时,这并不是因为附加到文件比覆盖单个文件更快。
在数据库系统中,如果您需要更新特定的记录,您必须首先找到您需要更新的实际文件(以及文件中的位置)。
这可能需要多次索引查找,更新记录后,您可能需要更新这些索引以反映新值。
所以正确的比较是附加到单个日志与进行多次读取然后多次写入。
我一直在做一些基准测试,试图了解 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 * 10
、b.N * 100
等),以便报告基准已适当调整。
关于(2),当一些系统宁愿使用顺序日志来存储操作以重播它们时,这并不是因为附加到文件比覆盖单个文件更快。
在数据库系统中,如果您需要更新特定的记录,您必须首先找到您需要更新的实际文件(以及文件中的位置)。
这可能需要多次索引查找,更新记录后,您可能需要更新这些索引以反映新值。
所以正确的比较是附加到单个日志与进行多次读取然后多次写入。