在C++中找到两个浮点数的最大值的最快方法是什么?

3
什么是查找两个浮点数中最大值的最快方法?
a)
y = std::max(x1, x2);

b)

if (x1 > x2)
    y = x1;
else
    y = x2;

c)

y = x1 > x2 ? x1 : x2;

谢谢


3
为什么不编写一个程序,分别执行数百万次每种类型的操作,然后测量每次操作所需的时间呢?很可能编译器无法区分b和c之间的差异。 - Stefan Thyberg
2
问题在于这个问题不能以一般意义上的方式回答。如果问题是在使用处理器X、操作系统Y、编译器Z和优化级别A时最快的方法是什么,那么也许会有一个有意义的答案。在所有其他情况下,答案都没有意义,因为有太多正确的答案,而这些答案在不同的情况下都是错误的。 - Martin York
1
同意Martin York的观点,这是一个太低级的操作,无法在语言层面上回答。例如,如果您正在为XBox 360编译,答案是“以上都不是”,因为您应该使用fsel操作码而不是fcmp。 - Not Sure
@Stefan,因为也许最快的方法不是这些之一,而基准测试也无法告诉你“使用汇编语言”。 - Thomas L Holaday
13个回答

24

这是一个不同的问题

你认为在整个程序的大环境中,这样一个微小的优化会有影响吗?

我觉得像这样的微小优化几乎不可能对程序产生可感知的影响。除非使用了性能分析器并明确指出了这是一个问题,否则不应该进行微优化。

编辑 添加一些评论中的澄清

代码性能非常依赖于以下因素:

  1. 在程序中使用该代码的方式
  2. 您正在使用的编译器
  3. 传递给编译器的优化标志
  4. 运行代码的特定架构
  5. 没有包含在问题中的许多其他非常微小的因素

即使所有这些信息都包括在内,我们的答案也只是猜测。回答这个问题的唯一方法是使用性能分析器并找出哪个更快。

然而,这几乎肯定不值得。微调程序中如此小的一部分几乎肯定不会为您的代码增加任何可感知的性能优势。通常,除非性能分析器明确告诉您出现了问题,否则最好不要像这样优化代码。否则,您会花费大量时间来优化没有可感知好处的东西。

是的,有些情况下这样的优化可能很重要。但这只会发生在非常特殊的情况下,其中代码是高度调用的紧密循环的一部分。然而,识别此类代码的唯一方法是使用性能分析器。


10
正如Donald Knuth所说:“我们应该忘记小的效率问题,大约有97%的时间:过早的优化是万恶之源。” - Emile Vrijdags
12
也许这是一个用户意见,但是似乎stackoverflow上有很多混乱,几乎每个优化问题的前N个答案都是这种变体。也许最好要么幽默地回答他们的问题,要么自动指向一个页面,提供这种类型的建议并问:“你确定要问这个问题吗?”,或者类似的方式。就记录而言,我认为你的建议非常好并且经常被忽略,但是当有充分的理由或者提问者只是出于好奇时,很难得到这样问题的有用答案。 - Matt J
5
@MattJ,我同意像我这样的许多答案之所以排名靠前,是因为它们是正确的。你不应该因为正确答案已经被说了很多遍就说错了答案。可能这是提问者第一次问这个问题或者看到这种类型的答案。而且正如Eric在一两天前所说,没有办法回答这个问题。这种微小优化的性能将与编译器、程序和提问者使用的具体架构密切相关。 - JaredPar
6
问题很直接,答案应该也很直接。无论是微小的优化还是不重要的优化都没关系。有些程序确实需要这样的优化,像这样微小的优化对这些特定的程序可能会产生巨大的影响。 同时,每个人都应该记住:C++是关于抽象和性能的,因此如果有一种更快的方法来完成任务而不损害程序的完整性,那么这种方法应该被采用。在编译最终版本时,所有东西都会相加。所以我认为这是一个有效的问题。 - chila
4
@chila,我的回答很直接,OP提出了错误的问题。我并不认为这个问题是无效的,我认为这仅仅表明OP在错误的地方寻找答案,正如我的回答所示。我不同意C++只关注性能。C++关注的是灵活性和按需付费。副作用是C++可以成为一个极具性能的语言。如果这对最终程序产生影响,分析器将显示该影响。这就是为什么您应该始终进行分析并基于此进行优化,而不是像“哪种方式的max更快”这样的项目。 - JaredPar
显示剩余12条评论

5
你可以在自己的系统上检查它。 我在gcc-redhat上为你完成了这项工作。我的系统执行了100,000次,其中x1 = 432943.5,x2 = 434232.9。 a) ~1200微秒 b) ~600微秒 c) ~600微秒
编辑: 使用-O2优化,在所有三种情况下我得到了相同的结果:~110微秒。 当然,实际结果取决于特定问题和系统中的许多因素。

1
你永远不想像那样计时单个语句。系统状态的变化太大了,无法准确计时单个语句。你需要计时一个大量迭代的循环。 - 17 of 26
1
现在加入缓存未命中的情况(这将更加真实),你会得到完全不同的数字。 - n0rd
2
你可以尝试打开像 -O2 这样的优化选项,这将允许编译器进行内联,从而得到更好的测试结果。 - Evan Teran
1
如果您没有使用任何标志进行编译,则测量就毫无意义了。此时GCC处于非常愚蠢的模式,不关心性能方面的任何内容。请尝试使用-O2标志。 - Johannes Schaub - litb
重新使用 -O2 进行了计算。在所有 3 种情况下,得到了相同的结果(110 微秒)。 - Igor
显示剩余3条评论

4

-O3双核Macbook Pro 2.4GHz

std::max(x1,x2)时间:4.19488 RMAAx的:4.19613 if时间:4.18775?时间:4.18831

std::max(x1,x2)时间:4.1836 RMAAx的:4.18274 if时间:4.18603?时间:4.18857

std::max(x1,x2)时间:4.18714 RMAAx's:4.18759 if时间:4.19752?时间:4.19797

std::max(x1,x2)时间:4.1926 RMAAx's:4.19293 if时间:4.19334?时间:4.19626

std::max(x1,x2)时间:4.18963 RMAAx's:4.19628 if时间:4.19253?时间:4.19107

#include <iostream>

using namespace std;

int main (int argc, char * const argv[]) {

    uint64_t iterations = 10000000000;
    float x1 = 3455.232;
    float x2 = 7456.856;
    float y = 0;

    for (int count = 0; count < 5; ++count)
    {       
        clock_t begin_time = clock();
        for (uint64_t ii = 0; ii < iterations; ++ii)
        {
            y = std::max(x1, x2);
        }

        std::cout << "std::max(x1, x2) Time: " << float( clock () - begin_time ) /  CLOCKS_PER_SEC << endl;


        begin_time = clock();
        for (uint64_t ii = 0; ii < iterations; ++ii)
        {
            y = x1;
            if (y < x2)
                y = x2;
        }

        std::cout << "RMAAx's : " << float( clock () - begin_time ) /  CLOCKS_PER_SEC << endl;


        begin_time = clock();
        for (uint64_t ii = 0; ii < iterations; ++ii)
        {
            if (x1 > x2)
                y = x1;
            else
                y = x2;
        }

        std::cout << "if Time: " << float( clock () - begin_time ) /  CLOCKS_PER_SEC << endl;


        begin_time = clock();
        for (uint64_t ii = 0; ii < iterations; ++ii)
        {
            y = x1 > x2 ? x1 : x2;
        }

        std::cout << "? Time: " << float( clock () - begin_time ) /  CLOCKS_PER_SEC << endl;
    }

    return 0;
}

4

这取决于不同的编译器,但我认为结果应该是相同的。请查看编译器生成的汇编代码。


3

英特尔x86处理器有指令(FCOMI/FCOMIP/FUCOMI/FUCOMIP),可以快速比较浮点数值。你的CPU也可能拥有这样的指令。关键是找出应该编写哪些C++代码,以最大化编译器使用这些指令的可能性,而不是执行某些更慢但更通用的操作。

乐观的建议是使用std::max(float, float),希望其他人忽略那些嘲讽“微基准测试”和“过早优化”的人,并进行必要的研究,为std::max(float, float)提供专门化,以使用你硬件的特殊指令。


+1 是为了表扬你的措辞得当,也让那些读过 Knuth 的人深思。 - rama-jka toti

2
这种方式可能更好吗?
y = x1;
if (y < x2)
    y = x2;

删除else条件可能会被编译器更好地解释。

编辑1:如果进行基准测试,请不要忘记将一半的测试用例中x1设置为大于x2,另一半则相反。否则,结果将不能反映真实情况。

编辑2:微优化在只有1或2k内存的嵌入式系统中是有用的。同时,思考为什么每种情况下的计时不同也是一个有趣的问题。


其实,这可能更糟,因为有50%的时间会有两个赋值! - Brian Postow
但是根据编译器的不同,在这种情况下有50%的时间不需要分支。显然,是否值得为节省50%的分支而支付50%的任务取决于架构和上下文。 - Steve Jessop

2
B和C在理论上至少编译出来是相同的。我选择它们,因为除非std::max不是一个函数调用(例如宏),否则它们将是最快的。 编辑 显然,std::max是一个类似于你的C形式的模板函数调用。 http://www.cplusplus.com/reference/algorithm/max/

3
std::max 不能是宏,但我希望它能够内联,除非有任何阻碍(如编译器选项)。 - Steve Jessop

2

通常情况下,当涉及到“最快”时,我们会有同样的观察。您是否测量了最大计算的执行时间,并将其报告给进程的其余执行时间?这个“优化”决策对应用程序的执行时间是否有重大影响?

我99%确定,您的建议之间的差异不值得考虑。


2

唯一真正了解它们的方法是测量它们。它们可能因编译器或平台而异。

循环运行每个项目100,000或500,000次,并比较总执行时间。


2
对它们进行基准测试并找出结果。

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