我对编译器了解不多,但知道它们足够复杂和智能,可以优化你的代码。 假设我的代码如下所示:
string foo = "bar";
for(int i = 0; i < foo.length(); i++){
//some code that does not modify the length of foo
}
GNU 编译器是否足够聪明,能够意识到
foo
的长度在此循环过程中不会改变,并用正确的值替换 foo.length()
调用? 或者每次 foo.length()
比较都会调用 i
?
由于 Mysticial 和 Kerrek 都正确地建议查看生成的程序集,因此这里有一个示例:
#include <string>
using namespace std;
int does_clang_love_me(string foo) {
int j = 0;
for (int i = 0; i < foo.length(); i++) {
j++;
}
return j;
}
我将上面的代码保存在test.cpp中并这样编译:
$ clang++ -o test.o -Os -c test.cpp
-Os 开关告诉 clang 尝试优化最小的代码大小。 GCC 有一个相应的开关可以使用。 为了查看程序集,我用 otool 敲击了生成的目标文件,因为我现在碰巧使用的是 Mac。 其他平台也有类似的工具。
$ otool -tv test.o
test.o:
(__TEXT,__text) section
__Z16does_clang_love_meSs:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp,%rbp
0000000000000004 movq (%rdi),%rax
0000000000000007 movq 0xe8(%rax),%rcx
000000000000000b xorl %eax,%eax
000000000000000d testq %rcx,%rcx
0000000000000010 je 0x0000001e
0000000000000012 cmpq $0x01,%rcx
0000000000000016 movl $0x00000001,%eax
000000000000001b cmoval %ecx,%eax
000000000000001e popq %rbp
000000000000001f ret
就像Mysticial说的;这只是一个变量访问。
唯一确定的方法就是尝试并查看装配。
我的猜测是,如果对
length()
的调用是内联的,那么 Loop Invariant Code Motion 会将 length()
的内部提升到循环之外,并将其替换为单个变量。
再想一想,这甚至可能没有实际意义。字符串的大小可能只是
string
类中的一个简单字段 - 它位于堆栈上。因此,仅内联对 length()
的调用就已经具有减少对简单变量访问的调用的效果。
编辑: 在后一种情况下,循环内是否修改
foo
的长度甚至并不重要。获取字符串的长度已经只是一个变量访问了。
编译器必须保证程序的行为就好像
length()
在每一轮中都被调用一样。只有当它能够证明没有副作用并且结果确实是恒定的时,它才能将调用提升到循环之外。
实际例子中发生的情况需要具体情况具体分析。如果你好奇的话,就看一下装配吧。
强制提升的典型方法是手动执行:
for (unsigned int i = 0, end = s.length(); i != end; ++i)
也许您还想考虑现代的
for (char & c : s)
作为替代方案。
老实说,我不知道 gcc 会如何优化这段代码。但将冗余代码移到循环外称为“部分冗余消除”。将 foo.length() 移到循环之外,称为循环不变代码移动,是部分冗余消除的一种形式。请看一下龙书第9.5节(我也在读这一章),它详细阐述了如何使用数据流分析来解决此类问题。这是斯坦福大学的幻灯片:http://suif.stanford.edu/~courses/cs243/lectures/l5.pdf。希望这些能有所帮助。