4

我正在使用 Apple LLVM 4.0 进行 ios 项目编译并进行优化。我实现了一个函数的两个不同版本,一个在 C 中,一个在 NEON 中。我想测试他们的表现。我的想法是将它们都调用相同的时间,然后在 Time Profiler 中查找它们以查看每个花费的相对时间。最初我的代码看起来像

used_value = score_squareNEON(patch, image, current_pos);
used_value = score_squareC(patch, image, current_pos);

当我分析时间时,NEON 代码根本没有出现。接下来我尝试了

for(int i = 0; i < successively_bigger_numbers; i++)
{
    used_value = score_squareNEON(patch, image, current_pos);
{
used_value = score_squareC(patch, image, current_pos);

NEON 代码仍然没有贡献。接下来是

used_value = score_squareNEON(patch, image, current_pos);
test = score_squareC(patch, image, current_pos);

测试从未被阅读过的地方。没有。然后

test = score_squareNEON(patch, image, current_pos);
test = 0;
other_used_variable += test;
used_value = score_squareC(patch, image, current_pos);

依然没有。最终使它执行这两个功能的是

value = score_squareNEON(patch, image, current_pos);
test = score_squareC(patch, image, current_pos);
...
min = (value+test)/2; //before it was min=value;

也非常重要。这两个函数都定义在我调用它们的同一个文件中。当我尝试将函数声明移动到不同的文件时,在每个示例中都会调用它们。

首先,我对编译器非常尊重。其次,我到底要做什么来确保调用一个函数?这让我开始质疑我之前计时的所有事情。如果在正常模式下

timerStart();
functionCall();
timerEnd();

中间的功能完全优化了吗?我是否需要每次都以某种方式开始检查,还是有什么技巧可以使用?编译器何时可以优化整个函数调用的规则是什么?

4

2 回答 2

5

也非常重要。这两个函数都定义在我调用它们的同一个文件中。当我尝试将函数声明移动到不同的文件时,在每个示例中都会调用它们。

当编译器可以证明一个函数调用没有副作用,并且它的结果没有被使用时,它可以移除这个调用。如果不能证明这一点,则无法删除该调用,因为据编译器所知,该函数可能具有副作用,并且不能消除这些副作用。

声明将函数调用的结果分配给的变量¹应该足以强制编译器将函数调用留在程序中(6.7.3,N1570 中的第 7 段):

具有 volatile 限定类型的对象可能会以实现未知的方式进行修改或具有其他未知的副作用。因此,任何引用此类对象的表达式都应严格按照抽象机的规则进行评估,如 5.1.2.3 中所述。此外,在每个序列点,最后存储在对象中的值应与抽象机规定的值一致,除非由前面提到的未知因素修改。构成对具有 volatile 限定类型的对象的访问是由实现定义的。

据我所知,对于 C++,保证不那么明确,但我认为 1.9 应该优先:

程序执行,1.9(6)和(7):

抽象机器的可观察行为是它对易失性数据的读取和写入顺序以及对库 I/O 函数的调用。6)

访问由 volatile 左值 (3.10) 指定的对象、修改对象、调用库 I/O 函数或调用执行任何这些操作的函数都是副作用,它们是执行环境状态的变化。表达式的评估可能会产生副作用。在被称为序列点的执行序列中的某些指定点,之前评估的所有副作用都应该是完整的,并且后续评估的副作用应该没有发生。

在 7.1.5.1 中:

[注意: volatile 是对实现的提示,以避免涉及对象的激进优化,因为对象的值可能会通过实现无法检测到的方式进行更改。详细语义见 1.9。一般来说,volatile 的语义在 C++ 中与在 C 中的语义相同。]

¹ 当然,这不适用于void fun()

于 2012-08-02T18:00:53.130 回答
4

编译器可以对您的代码做任何它想做的事情,只要“可观察”的结果与您在该语言定义的理想化“虚拟机”上编写的代码完全一样,无法区分。

“可观察”不包括运行时间、分析器结果、通过调试器观察到的变量等。可观察行为被认为是对易失性对象的访问、写入文件的数据以及对输入和输出设备的处理。

因此,要确保您的代码实际运行,您需要确保它必须运行才能产生正确的可观察行为。通常,您可以只保存要打印或写入文件的输出(在您计时的代码之外)。另一种选择是将输出写入 volatile 变量。

另一件事可能很重要,如果编译器可以静态评估您的代码,那么即使您打印输出,函数调用也可能减少为仅加载编译器的静态计算输出。为避免这种情况,您可能必须向函数提供无法静态知道的输入,例如从输入或文件或易失性变量中读取的数据。


当然,以您不会在实际程序中使用的方式使用输入和输出会影响时序。因此,衡量性能最可靠的方法是在具有您希望测试的配置的真实程序中进行。编写程序以便在配置之间轻松切换,然后测试两者。

于 2012-08-02T18:01:01.167 回答