写或者printf,哪个更快?

13

进行以下测试后:

for( i = 0; i < 3000000; i++ ) {
    printf( "Test string\n" );
}

for( i = 0; i < 3000000; i++ ) {
    write( STDOUT_FILENO, "Test string\n", strlen( "Test string\n" ) );
}

结果发现,printf函数的调用总共只花费了3秒钟,而write函数的调用则花费了高达46秒钟。考虑到printf有所有的格式化魔法,并且printf本身也会调用write函数,这是怎么可能的呢?我有什么遗漏了吗?

非常感谢您提出的任何想法和建议。


11
真的吗?你每次都在计算字符串的长度,然后将其作为时间测量的一部分吗? - K-ballo
3
优化后,这个应该只在编译期间计算一次。 - Daniel Fischer
@Daniel:那似乎意味着,printf 所做的缓冲(正如 Paul 所指出的)是其写入文件时的主要优势。 - Mike Dunlavey
2
puts 应该会更快。 - Jens Gustedt
@K-ballo 我进行这个时间测试的主要原因是,当我做系统应用程序时,通常都是这样做的。这主要是因为我并不总是确定特定系统如何处理空终止符。或者,在最坏的情况下,我甚至不知道字符串的长度。这似乎是评估我通常的系统应用程序性能最公正的方式。而且,正如Daniel Fischer所说,当涉及到字面量字符串时,优化器应该能够很好地处理它。 - rurouniwallace
显示剩余4条评论
2个回答

31
你遗漏了一些细节。printf 不一定每次都调用 write 函数,它会将输出缓存到内存中,只有当缓存区满或某些条件触发时才会调用 write 函数。因为 write 函数调用相对较慢,所以减少调用次数可以提高性能。如果你的标准输出重定向到一个终端设备,则每次看到 \n 时 printf 都会调用 write 函数;如果标准输出重定向到文件或 /dev/null,则 printf 只有在其内部缓冲区满时才会调用 write 函数。此外,缓冲区大小的选择应该是典型扇区大小的倍数,这样系统可以最高效地将数据写入磁盘。如果缓冲区太小,则可能需要读取扇区中的原始数据、修改它并重新写出结果,这会影响性能。第一个循环中调用 write 函数的次数约为 3000000/(4096/12) == 8780;而第二个循环则调用了 3000000 次 write 函数。

我预计你的第一个循环会比第二个快得多。


1
这解释得很清楚,谢谢!关于您对数据大小的评论...即使它完美地适合扇区,为什么原始数据不需要被读取呢?难道写调用仍然不需要知道需要写入的数据吗? - rurouniwallace

5

你不能将苹果和橙子进行比较,因为带有write的循环会运行strlen3000000次,而printf不会执行任何操作。它也不执行任何格式化操作,因此“花式格式化魔术”几乎没有用处。

size_t len = strlen( "Test string\n" );
for( i = 0; i < 3000000; i++ ) {
    write( STDOUT_FILENO, "Test string\n", len );
}

另一个重要的区别是,printf 在传递\n时每次都会刷新,而write不会。为了使您的基准测试更加公平,您应该从两个字符串中删除\n

3
在我的系统上,即使没有进行优化,gcc-4.5.1也会在编译时计算strlen。冲洗/缓冲似乎是导致差异的原因。 - Daniel Fischer
2
如果输出被重定向到文件,则 printf 不会在每个 \n 上刷新。此外,更准确地说,无论内容如何,write 每次都会刷新 -- 毕竟,在这种情况下,“刷新”仅意味着“调用 write”。 - Robᵩ
你为什么认为printf不需要实现strlen的效果? - Chris Stratton
1
@dasblinkenlight,要将一个以null结尾的字符串复制到目标位置,必须计算它的长度或逐个字符复制。 - Chris Stratton
1
@ChrisStratton 但是当您逐个输出字符串字符时,仍然只需从内存中读取每个字符一次,而不是两次。无论是write还是printf都需要将字符串中的所有字符移动到I/O系统拥有和管理的某个位置,从该位置最终将这些字符传输到磁盘或tty(我故意避免使用“缓冲区”一词)。 printf会在达到\0字符时停止;write会在传入的计数耗尽时停止。 - Sergey Kalinichenko
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接