为什么gcc和clang没有将strlen从这个循环中提升出来?

ein*_*ica 8 c gcc clang compiler-optimization hoisting

请考虑以下代码:

#include <string.h>

void bar(char c);

void foo(const char* __restrict__ ss) 
{
    for (int i = 0; i < strlen(ss); ++i) 
    {
        bar(*ss);
    }
}    
Run Code Online (Sandbox Code Playgroud)

我希望strlen(ss)在这些基本理想的条件下将其提升出来; 然而 - 它不是,既不是由clang 5.0也不是由gcc 7.3和最大优化(-O3).

为什么会这样?

注意:灵感来自(我的回答)这个问题.

use*_*ica 5

其他答案声称strlen呼叫无法提升,因为字符串的内容可能会在呼叫之间发生变化.这些答案没有恰当地解释其语义restrict; 即使bar通过全局变量或其他机制访问字符串,restrict指向const类型的指针的语义应该(请参阅警告)禁止bar修改字符串.

C11,N1570草案,6.7.3.1:

1设D是普通标识符的声明,它提供了一种将对象P指定为类型T的限制限定指针的方法.

2如果D出现在块内并且没有存储类extern,则让B表示该块.如果D出现在函数定义的参数声明列表中,则让B表示关联的块.否则,让B表示主块(或在独立环境中在程序启动时调用的任何函数块).

3在下文中,指针表达式E被称为基于对象P if(在评估E之前执行B的某个序列点)修改P以指向其先前的数组对象的副本指向会改变E的值.137)注意''based''仅针对具有指针类型的表达式定义.

4在每次执行B期间,让L为任何具有&L基于P的左值.如果L用于访问它指定的对象X的值,并且X也被修改(通过任何方式),则以下要求apply:T不具有const限定条件.用于访问X值的每个其他左值也应具有基于P的地址.出于本子条款的目的,每次修改X的访问也应被视为修改P. 如果为P分配了指针表达式E的值,该指针表达式E基于与块B2相关联的另一个受限指针对象P2,则B2的执行应在执行B之前开始,或者B2的执行应在该执行之前结束.分配.如果不满足这些要求,则行为未定义.

5这里B的执行意味着程序执行的一部分对应于具有标量类型的对象的生命周期和与B关联的自动存储持续时间.

这里,声明Dconst char* __restrict__ ss,并且相关的块B是主体foo.strlen访问字符串的所有左值都&L基于ss(参见警告),并且这些访问在执行期间发生B(因为,通过第5节中的定义,执行strlen是执行的一部分B).ss指向const限定类型,因此在第4节中,允许编译器假定strlen在执行期间未被修改的字符串元素被修改foo; 修改它们将是未定义的行为.

(警告)上面的分析假设strlen通过"普通"指针解除引用或索引来访问字符串.如果strlen使用SSE内在函数或内联汇编等技术,我不清楚这种访问在技术上是否计算为使用左值来访问它指定的对象的值.如果它们不计算,则保护restrict可能不适用,并且编译器可能无法执行提升.

也许上述警告会使保护措施无效restrict.也许编译器strlen对分析其交互的定义restrict知之甚少(我很惊讶它没有内联).也许编译器可以自由地执行吊装并且没有意识到它; 可能没有实现某些相关优化,或者它无法在正确的编译器组件之间传播必要的信息.确定确切的原因需要比我拥有的GCC和Clang内部更加熟悉.

消除strlen和循环的进一步简化测试显示Clang肯定对限制指针到const优化有一些支持,但我无法从GCC观察到任何此类支持.


Bas*_*tch 1

ss可能是一些全局变量,因为您可以foo使用一些全局数组作为char str[100];其参数进行调用(例如,通过foo(str);在您的main)...

\n\n

并且bar可以修改该全局变量(然后strlen(ss)可以在每个循环中更改)。

\n\n

BTWrestrict也许不是你所相信的意思。仔细阅读C11 标准的\xc2\xa76.7.3节和\xc2\xa76.7.3.1。恕我直言,restrict实际上对同一函数的两个形式参数最有用,以表达它们不是“别名”或“重叠”指针的事实(如果你猜到我真正的意思),也许还可以进行优化工作restrict在此类情况。

\n\n

也许(但不太可能),在您的特定程序上,如果您将其调用gcc -flto -fwhole-program -O3为(在每个翻译单元上和程序链接时),我不会打赌(但我让你去检查)。

\n\n
\n

为什么会出现这种情况呢?

\n
\n\n

至于为什么当前的GCC(或Clang)没有像你想要的那样进行优化,这是因为没有人编写过这样的优化过程并在-O3.

\n\n

编译器不需要进行优化,只允许进行其中一些优化(由其实现者选择)。

\n\n

由于它是免费软件,请随时通过向 GCC贡献来提出补丁(或 Clang)你可能需要一整年的工作,并且你不确定你的优化是否会被接受(因为实际上没有人像你展示的那样编写代码,或者因为你的优化太具体,所以不太可能被触发,但仍然减慢了速度)编译器)。但欢迎您尝试。

\n\n

即使 \xc2\xa76.7.3.1允许您的优化(如user2357112 的答案所示),实际上可能不值得付出努力来实现它。

\n\n

(我的直觉是,实现这样的优化很困难,并且在实践中不会给现有程序带来太多好处)

\n\n

顺便说一句,您绝对可以通过编写一些GCC 插件来实验这样的优化(因为插件框架是为此类实验而设计的)。您可能会发现,编写这样的优化代码需要大量工作,而且实际上,它并不能提高大多数现有程序(例如在 Linux 发行版中)的性能,因为很少有人那样编写代码。

\n\n

GCC 和 Clang 都是自由软件项目,它们的贡献者都是(从 FSF 等角度来看)志愿者。因此,请随意改进 GCC(或 Clang),就像您希望它优化一样。根据过去的经验,即使向 GCC 贡献一小段代码也需要花费大量时间。而且GCC是一个庞大的程序(大约有千万行代码),因此了解其内部结构并不容易。

\n

  • 不幸的是,“restrict”并没有您想要的(强大的)含义。 (3认同)