我正在研究和ios项目使用Apple LLVM 4.0进行优化编译。我实现了两个不同版本的函数,一个在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();
中间的功能完全优化了吗?我是否需要每次都以某种方式开始检查,或者是否有我可以使用的技巧?当编译器可以优化整个函数调用时有哪些规则?
答案 0 :(得分:5)
同样非常重要。这些函数都在我调用它们的同一个文件中定义。当我尝试将函数声明移动到另一个文件时,在每个示例中都会调用它们。
当编译器可以证明函数调用没有副作用,并且未使用其结果时,它可以删除该调用。如果无法证明,则无法删除调用,因为只要编译器可以知道,该函数可能有副作用,而且不能消除。
声明变量将函数调用的结果赋给¹应该足以强制编译器在程序中保留函数调用(6.7.3,N1570中的第7段):
具有挥发性限定类型的对象可能会以未知的方式进行修改 实施或有其他未知的副作用。因此任何表达都是指 对这样的对象应严格按照抽象机的规则进行评估, 如5.1.2.3中所述。此外,在每个序列点上最后存储的值 对象应与抽象机器规定的内容一致,除非经过修改 前面提到的未知因素。是什么构成对对象的访问 具有volatile-quali fi ed类型的是实现定义的。
对于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()
。
答案 1 :(得分:4)
只要“可观察”结果与在语言定义的理想化“虚拟机”上编写完全相同的“可观察”结果无法区分,编译器就可以对代码执行任何操作。
'observable'不包括运行时间,分析器结果,通过调试器观察到的变量等。可观察行为被认为是对易失性对象的访问,写入文件的数据以及输入和输出设备的处理。 / p>
因此,为了确保您的代码实际运行,您需要确保必须运行它才能生成正确的可观察行为。通常,您可以保存要打印或写入文件的输出(在您正在计时的代码之外)。另一种选择是将输出写入volatile变量。
另一件可能重要的事情是,如果编译器可以静态评估您的代码,那么即使您打印输出,函数调用也可能会减少到只加载编译器的静态计算输出。为避免这种情况,您可能必须为无法静态知道的函数提供输入,例如从输入或文件或volatile变量读取的数据。
当然,使用输入和输出的方式在实际程序中不会影响时序。因此,最可靠的衡量性能的方法是在具有您希望测试的配置的真实程序中这样做。编写程序,以便在配置之间轻松切换,然后测试两者。