【问题标题】:What do I have to do to get a function called in compiler optimized code?我该怎么做才能获得在编译器优化代码中调用的函数?
【发布时间】:2012-08-03 02:22:06
【问题描述】:

我正在使用 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();

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

【问题讨论】:

  • 我很好奇,在您的原始测试中,当函数调用被优化时,静态分析器是否会通知您某些值永远不会被读取?当编译器认为你正在做的事情毫无意义时,Clang 的输出可以作为粗略的指导。
  • 从我的简短测试看来,静态分析器警告似乎是必要的,但还不够。当函数在同一个文件中时,它会警告所有情况,说明存储在 test 中的值永远不会被读取。当函数声明移动到不同的文件时,它会继续发出警告,在这种情况下,函数调用不会被优化。

标签: c++ ios optimization compilation llvm


【解决方案1】:

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

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

声明函数调用结果分配给的变量¹应该足以强制编译器将函数调用留在程序中(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()

【讨论】:

    【解决方案2】:

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

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

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

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


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

    【讨论】:

    • 谢谢,我尝试将测试更改为 volatile 并且它有效。以后我会记住这一点
    猜你喜欢
    • 2013-04-18
    • 1970-01-01
    • 2016-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 2018-06-24
    • 1970-01-01
    相关资源
    最近更新 更多