问题 在比较用C ++编写的两种不同算法时,您使用的优化级别(g ++)是多少?


我有两个用C ++编写的算法。据我所知,编译时是常规的
-O0 -NDEBUG(g ++)在比较两种算法的性能时(无症状地,它们是相同的)。 但我认为优化级别对其中一个是不公平的,因为它在每种情况下都使用STL。使用普通数组的程序在使用-O0选项编译时,速度比STL重算法快5倍。但是当我使用-O2 -NDEBUG编译它们时,性能差异并没有太大的不同。

有没有办法在优化级别-O0中充分利用STL(我在vector []运算符中获得了很高的性能)?

在比较两种算法时,您使用了哪些优化级别(以及可能的变量,如-NDEBUG)?

如果有人能够对比较C ++编写的算法性能的学术研究趋势有所了解,那将会有很大的帮助吗?

编辑:

好的,为了隔离优化级别的问题,我现在使用一种算法但两种不同的实现。

我用原始指针(int和boolean)将其中一个函数改为std :: vector和std :: vector ...使用-O0 -NDEBUG,性能为5.46s(原始指针)和11.1s(std :: vector) )。使用-O2 -NDEBUG,性能为2.02s(原始指针)和2.21s(std :: vector)。相同的算法,一个实现使用int和boolean的4/5动态数组。另一个是使用std :: vector和std :: vector代替。在其他所有情况下都是一样的

你可以看到-O0 std :: vector的性能比指针快了两倍。虽然在-O2中它们几乎相同。

但我真的很困惑,因为在学术领域,当他们在运行时发布算法结果时,他们用-O0编译程序。

我缺少一些编译器选项吗?


7384
2017-10-03 06:22


起源

我担心那些“学术领域”在这一点上是错误的。不优化会不公平地惩罚通过单独的部分拆分成单独的函数而合理编写的代码,不会将局部变量重新用于单独的用途,并且一般不会被编写为一个大的,微优化的,兔子函数。 - CB Bailey
如果您想将其纳入研究论文,只需列出所有优化级别的数据,并解释差异。优秀的学者提供所需的所有信息,使他们的结果可重复和可理解。我怀疑通常表现 区别 两种算法之间的差异不会很大程度上取决于进行测量的优化级别。在那些情况下,只是坚持-O0显然是常见的,似乎是有道理的。但对你而言很重要,你可以解释一下,你很好。 - avandeursen
我同意Kaka Woef和Charles Bailey,但也会补充说算法之间的比较恰恰相反,而不是STL与非STL或其他实现风格的比较。你应该确保你正在比较你关心的事情。如果真实世界的性能是您关心的,请使用您用于实际构建的选项进行编译。只要确保你说明你使用哪些选项,并对两者使用相同的选项 - 即使比较是为了你自己的利益,它可以在以后节省混淆。 - Steve314


答案:


这取决于您想要优化的内容。

速度

我建议使用 -O2 -NDEBUG -ftree-vectorize,如果您的代码专门设计为在x86或x86_64上运行,请添加 -msse2。这将为您提供有关GIMPLE如何表现的广泛想法。

尺寸

我相信你应该用 -Os -fno-rtti -fno-exceptions -fomit-frame-pointer。这将最小化可执行文件的大小到一定程度(假设C ++)。


在这两种情况下,算法的速度都不依赖于编译器,但是如果编译器可以“证明”它可以,则编译器可以大大改变代码的行为方式。

GCC检测“常用”代码,例如手动编码 min() 和 max() 并将它们转换为一条SSE指令(在x86 / x86_64上,当设置了-msse时)或在i686可用时使用cmov(SSE具有更高的优先级)。 GCC还可以自由地重新排序循环,展开和内联函数,如果它想要,甚至删除无用的代码。

至于你最新的编辑:

你可以看到在-O0 std :: vector中   跑得快两倍   指针。虽然在-O2他们几乎   一样。

那是因为 std::vector 仍然有代码抛出异常并可能使用rtti。尝试比较 -O2 -NDEBUG -ftree-vectorize -fno-rtti -fno-exceptions -fomit-frame-pointer,你会发现std :: vector会比你的代码略胜一筹。 GCC知道什么是“内置”类型以及如何利用它们 在现实世界中使用 并乐意这样做 - 就像它知道什么 memset() 和 memcpy() 当复制尺寸已知时,是否以及如何相应地进行优化。


7
2017-10-03 06:33



主要重点是比较,而不是优化。但我想现代库的编写方式是,如果没有优化,手写代码就会慢一些。 - hasan
STL在未经优化时非常重。 GCC将生成代码 因为它看到了它 并且不会花费一秒钟来优化。 STL被设计用于很多情况,因此专门的代码可能会胜过它 - 但是如果你写的话 完全一样 在它背后的想法,STL将赢得因为答案中所述。 - LiraNuna
std :: vector :: at可以合法地抛出异常,任何导致重新分配的函数也可能抛出异常。 -fno-rtti 和 -fno-exceptions 导致gcc偏离符合C ++的支持并将破坏代码。 - CB Bailey
它不会破坏代码。如果GCC认为代码依赖于异常,则无法编译。对于-fno-rtti也是如此。如果您的代码没有 try {} catch {} 在任何地方,编译器都不会抛出物理异常是“安全的” - 而是做相同的 puts 和 exit(1)。 - LiraNuna
如果代码无法执行,你如何破解代码呢?海湾合作委员会非常聪明,不要因为你7年前读到的内容而低估它。 - LiraNuna


编译器优化通常不会改变算法的复杂度顺序,只会改变常量和线性比例因子。编译器相当聪明,但它们不是  聪明。

你打算用-O0编译你的代码以便发布吗?可能不会。您可以在编译时使用您实际打算使用的任何编译标志来比较算法的性能。


6
2017-10-03 06:28





你有两种算法 实施 在C ++中。如果要比较两个实现的相对性能,则应使用将在最终产品中使用的优化级别。对我来说,就是这样 -O3

如果您想分析算法的复杂性,那么更多的是分析问题,您可以查看必须针对不同大小和输入特征执行的操作的总体计数。

作为开发人员编写性能问题的代码,最好注意编译器可以并且可能适用于您的代码的优化范围。不优化不公平地惩罚清楚编写的代码,但设计为易于针对已经“微优化”的代码进行优化。


2
2017-10-03 06:33



-O3 是 非常 侵略性。它可以破坏法律代码。 - LiraNuna
-O3 不允许破坏合法代码,这将构成编译器错误。你有参考吗? - CB Bailey
同样,主要关注的不是优化,而是要进行比较。 - hasan
我曾经在没有优化的情况下编译进行所有比较,但使用STL有点迫使我使用优化。 - hasan
@LiraNuna - 我也希望看到-O3破坏代码的参考。我见过的唯一被-O3破坏的代码是开始时出错的代码,但不幸的是在较低的优化级别上“出现”正确。 - Tom


我认为没有理由不在O2编译和运行它们。除非你把它作为纯粹的学术练习(即使你不太可能,优化会对算法的属性产生根本性的改变 - 尽管如此,我想如果GCC开始转向O(N)我会很高兴源自O(lgN)程序集),您将需要的信息与实际运行最终程序时的信息一致。您很可能不会使用O0优化发布程序,因此您不希望在O0优化下比较算法。


1
2017-10-03 06:30





这种比较不是关于公平而是产生有用的信息。如果代码投入生产使用,您应该使用计划使用的优化级别。如果你基本上做研究,那么  如果没有亲自计划将其投入生产使用,你就会陷入更难以猜测将投入生产的人可能做的事情。

实际上,即使你正在进行开发而不是研究,你仍然会遇到一些问题 - 几乎不可能预测你最终可能会使用这个特定代码的优化级别。

就个人而言,我通常使用-O2和gcc。我的一般经验法则是使用打开自动内联的最低级别的优化。我编写了很多代码,希望编译器能够内联小函数 - 并专门编写代码以帮助完成(例如经常使用函子而不是函数)。如果编译器没有设置为那些内联生成代码,那么你就没有得到我真正想要的东西。当代码编译时,代码的性能并不意味着什么 - 我当然会这样做  计划用这种方式真正使用它。


0
2017-10-03 14:19