3

我了解大多数微优化,但它们真的有用吗?

Exempli gratia:做++i而不是i++while(1)或者for(;;)真的会导致性能改进(在内存指纹或 CPU 周期方面)?

所以问题是,在 C 中可以进行哪些微优化?它们真的有用吗?

4

13 回答 13

20

你应该依靠你的编译器来优化这些东西。专注于使用适当的算法并编写可靠、可读和可维护的代码。

于 2010-01-21T12:58:00.377 回答
5

tclhttpd,一个用 Tcl 编写的网络服务器,最慢的脚本语言之一,成功地超越了 Apache,一个用 C 编写的网络服务器,据说是最快的编译语言之一。使用更快的算法/技术*。

永远不要担心微优化,直到您可以在调试器中证明这是问题所在。即便如此,我还是建议先来这里,询问是否是个好主意,希望有人能说服你不要这样做。

这是违反直觉的,但很多时候代码,尤其是紧密的嵌套循环或递归,是通过添加代码而不是删除它们来优化的。游戏行业已经提出了无数技巧来使用过滤器加速嵌套循环以避免不必要的处理。这些过滤器添加的指令比 i++ 和 ++i 之间的差异要多得多。

*注:从那时起我们学到了很多东西。由于产生线程的成本很高,因此缓慢的脚本语言可以胜过编译的机器代码这一认识导致了 lighttpd、NginX 和 Apache2 的发展。

于 2010-01-21T13:42:21.723 回答
4

我认为,微优化、技巧和做某事的替代方法之间是有区别的。++i它可以是使用而不是 的微优化i++,尽管我认为它只是避免悲观,因为当您预先递增(或递减)时,编译器不需要插入代码来跟踪变量的当前值用于表达式。如果使用预增量/减量不会改变表达式的语义,那么您应该使用它并避免开销。

另一方面,一个技巧是使用非显而易见机制的代码比直接机制更快地实现结果。除非绝对需要,否则应避免使用技巧。获得一小部分的加速通常不值得损害代码的可读性,除非这个小百分比反映了有意义的时间量。运行时间极长的程序,尤其是计算量大的程序或实时程序通常是技巧的候选者,因为节省的时间量可能是满足系统性能目标所必需的。如果使用技巧,应清楚地记录在案。

替代品,仅此而已。性能提升可能没有或很少;它们只是代表表达相同意图的两种不同方式。编译器甚至可以生成相同的代码。在这种情况下,请选择最易读的表达式。我会说这样做,即使它会导致一些性能损失(尽管参见上一段)。

于 2010-01-21T13:08:12.840 回答
4

我认为您不需要考虑这些微优化,因为它们中的大多数都是由编译器完成的。这些东西只会让代码更难阅读。

请记住,[edited] 过早的 [/edited] 优化是一种邪恶。

于 2010-01-21T12:55:05.640 回答
3

老实说,这个问题虽然有效,但与今天无关——为什么?

编译器编写者比 20 年前聪明得多,可以及时回溯,然后这些优化将非常相关,我们都在使用旧的 80286/386 处理器,而编码器经常会使用技巧来挤压更多编译代码中的字节。

今天,处理器太快了,编译器编写者知道操作数指令的私密细节以使每件事都能正常工作,考虑到有管道、核心处理器、数英亩的 RAM,请记住,使用 80386 处理器,将有 4Mb RAM 和如果幸运的话,8Mb 被认为是优越的!

范式发生了变化,它是关于从编译代码中挤出每个字节,现在它更多地关注程序员的生产力并更快地发布版本。

上面我已经说明了处理器和编译器的性质,我说的是 Intel 80x86 处理器系列,Borland/Microsoft 编译器。

希望这会有所帮助,最好的问候,汤姆。

于 2010-01-21T13:04:57.383 回答
3

如果您可以很容易地看到两个不同的代码序列产生相同的结果,而不对代码中存在的数据做出假设,那么编译器也可以,而且通常会。

仅当从一个到另一个的转换非常不明显或需要假设您可能知道是正确的但编译器无法推断的情况(例如,操作不能溢出或两个指针永远不会别名,即使它们没有用restrict关键字声明)你应该花时间考虑这些事情。即使这样,最好的办法通常是找到一种方法来通知编译器它可以做出的假设。

如果您确实发现编译器错过了简单转换的特定情况,那么 99% 的时间您应该只向编译器提交一个错误并继续处理更重要的事情。

于 2010-01-21T15:02:01.000 回答
2

牢记内存是新磁盘这一事实可能会比应用任何这些微优化更能提高您的性能。

于 2010-01-21T14:05:43.030 回答
1

有关 ++i 与 i++(至少在 C++ 上下文中)问题的更务实的看法,请参阅http://llvm.org/docs/CodingStandards.html#micro_preincrement

如果 Chris Lattner 这么说,我必须注意。;-)

于 2010-01-21T13:21:40.947 回答
1

您最好将您编写的每个程序主要视为一种语言,您可以在其中将您的想法、意图和推理传达给其他必须修复错误、重用和理解它的人。他们将花费更多的时间来解码乱码,而不是任何编译器或运行时系统执行它。总而言之,使用相关语言的常用习语,以最清晰的方式说出你的意思。

对于 C 中的这些特定示例,for(;;) 是无限循环的惯用语,而“i++”是“i 加一”的常用惯用语,除非您在表达式中使用该值,在这种情况下,这取决于是否含义最清楚的值是增量之前或之后的值。

于 2010-01-21T13:47:26.610 回答
1

根据我的经验,这是真正的优化

SO上有人曾经说过,微优化就像“理发减肥”。美国电视台有一档节目叫《最大的输家》,肥胖的人竞相减肥。如果他们能够将体重减轻到几克,那么理发会有所帮助。

也许这夸大了与微优化的类比,因为我已经看到(并编写了)微优化确实产生影响的代码但是当开始时,仅仅不解决你没有解决的问题会收获更多有。

于 2010-03-02T15:50:43.667 回答
0

对于不使用返回值的情况,++i 应该优于 i++,因为它更好地代表了您尝试执行的操作的语义(增加 i)而不是任何可能的优化(它可能会稍微快一些,并且是可能不会更糟)。

于 2010-01-21T13:12:20.117 回答
0

通常,向零计数的循环比向其他数字计数的循环快。我可以想象这样一种情况,编译器不能为你做这个优化,但你可以自己做。

假设您有一个长度为 x 的数组,其中 x 是一个非常大的数字,并且您需要对 x 的每个元素执行一些操作。此外,假设您不在乎这些操作发生的顺序。您可能会这样做......

int i;
for (i = 0; i < x; i++)
    doStuff(array[i]);

但是,您可以通过这种方式进行一些优化 -

int i;
for (i = x-1; i != 0; i--)
{
    doStuff(array[i]);
}
doStuff(array[0]);

编译器不会为您执行此操作,因为它不能假定顺序不重要。

MaR 的示例代码更好。考虑一下,假设 doStuff() 返回一个 int:

int i = x;
while (i != 0)
{
    --i;
    printf("%d\n",doStuff(array[i]));
}

只要可以接受以相反的顺序打印数组内容就可以了,但是编译器无法为您决定。

这是一种优化取决于硬件。根据我对编写汇编程序的记忆(很多很多年前),每次通过循环时,向上计数而不是向下计数到零需要额外的机器指令。

如果您的测试类似于 (x < y),那么测试的评估将如下所示:

  • 从 x 中减去 y,将结果存储在某个寄存器 r1 中
  • 测试 r1,设置 n 和 z 标志
  • 基于 n 和 z 标志的值进行分支

如果您的测试是 ( x != 0),您可以这样做:

  • 测试 x,设置 z 标志
  • 基于 z 标志值的分支

您可以跳过每次迭代的减法指令。

有些架构可以让减法指令根据减法的结果设置标志,但我很确定 x86 不是其中之一,所以我们大多数人都没有使用可以访问这样的编译器机器指令。

于 2010-01-21T14:40:36.040 回答
0
x ^= y
y ^= x
x ^= y
于 2010-01-21T12:55:06.443 回答