我能指望编译器优化const char *上的strlen吗?

17

在我的SAX XML解析回调函数(XCode 4,LLVM)中,我要执行很多类似于以下代码的调用:

static const char* kFoo = "Bar";

void SaxCallBack(char* sax_string,.....)
{
     if ( strcmp(sax_string, kFoo, strlen(kFoo) ) == 0)
     {

     }


  }

可以假设strlen(kFoo)是由编译器进行了优化的吗?

(苹果的示例代码已经预先计算了strlen(kFoo),但我认为对于大量常量字符串来说,这种方法容易出错。)

编辑:进行优化的动机是在iPod touch 2G上解析我的SVG地图需要5秒(!),使用NSXMLParser。因此,我想切换到lib2xml,并优化字符串比较。


优化建议给初学者:不要。优化建议给专家:还没有。除非分析表明这是程序瓶颈所在的地方,否则我认为你不应该担心它。 - freespace
3
你是指 strncmp,对吧?因为你可以只使用带有两个参数的 strcmp,它与当前写法的条件等效。 - Pascal Cuoq
6
问题是“我能指望我的编译器进行优化吗?”。除非您认为这个建议适用于编译器,否则我不明白它与这个问题有什么关联。 - Pascal Cuoq
2
除非语言标准规定或您在使用前已经检查了10次,否则几乎不可能安全地假设编译器会对某些内容进行优化。因此,我的建议是检查生成的汇编代码 :) - Johan Kotlinski
如果有影响的话,你可以检查一下。但是除非那段代码在“实时”系统中被调用了无数次,否则这并不重要。即使在这种情况下,可能还有许多地方需要手动优化才能更有效。这些字符串常量也不会很长,像一百个单词那么多。 - user395760
2
@Pascal Cuoq,这与问题的根本原因相关:Jacko认为它需要优化,这就是他问编译器是否会为他完成的原因。我相信了解何时调查编译器优化、何时调查算法优化以及何时不浪费时间进行优化是有价值的。 - freespace
4个回答

13

如果你说的“LLVM”是指clang,那么是的,你可以依靠clang -O优化掉strlen。这是你的函数代码:

_SaxCallBack:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    leaq    L_.str1(%rip), %rsi
    movl    $3, %edx
    callq   _strncmp
    ...

我将strcmp改为strncmp,但第三个参数实际上已经被立即的$3替换。

请注意,gcc 4.2.1 -O3不会优化这个strlen调用,并且您只能期望它在您问题的精确条件下(特别是字符串和调用strlen必须在同一个文件中)才能正常工作。


1
我检查了微软编译器。在使用/O2优化时,它也会优化调用。 - Mark Wilkins
3
如果将 kFoo 定义为 static const char * const kFoo = "Bar"; 常量的话,gcc 将优化掉 strlen() 函数的调用。 - caf
clang version 2.8 在我的机器上没有对 strlen() 进行优化,除非加上第二个 const - jfs
1
GCC 4.5及以上版本会根据在gcc.godbolt.org上进行的快速测试来进行优化:http://goo.gl/c9dgiq - user31389

13

不要写出以下类似的代码:

static const char* kFoo = "Bar";
你创建了一个名为kFoo的变量,它指向常量数据。编译器可能能够检测到这个变量不会改变并将其优化掉,但如果不能,你就会使程序的数据段膨胀。

同时,也不要写出这样的代码:

static const char *const kFoo = "Bar";

现在你的变量 kFooconst 限定的且不可修改的,但如果它在位置无关代码(共享库等)中使用,则其内容仍会在运行时发生变化,从而为程序增加启动和内存成本。相反,应使用:

static const char kFoo[] = "Bar";

或者甚至:

#define kFoo "Bar"

8
我不建议在C++中使用#define选项,绝对不推荐!感谢您提醒指针问题。 - Matthieu M.
1
这个问题标记为C,而不是C ++。但是,无论如何我都更喜欢static const char []形式。 - R.. GitHub STOP HELPING ICE
11
static const char * 的问题在于它创建了一个实际变量,其内容是另一个静态存储期对象(即字符串文字)的地址。由于该值取决于加载地址,因此必须在加载时通过重定位进行修补。而对于static const char[],地址不驻留在任何静态存储期对象中,引用该数组的代码可以通过干净的位置无关PC相对地址访问它,这个地址在链接时固定。 - R.. GitHub STOP HELPING ICE
2
据我所知,另一方面,在GCC中声明一个数组实际上会导致整个文字面值逐块复制到堆栈上。在这种情况下,我们必须权衡是否要支付代码修补或堆栈分配的代价。我说得对吗? - Armen Michaeli
3
如果我们谈论具有自动存储期的对象,你说得没错,但是这些对象具有静态存储期。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论

3
一般来说,你不能指望它。但是,你可以使用“sizeof”并将其应用于字符串文字。当然,这意味着你不能像最初定义“kFoo”那样定义它。
以下内容适用于所有编译器和所有优化级别。
#define kFoo "..."

    ... strcmp(... sizeof(kFoo))

6
注意,在这种情况下,sizeof(kFoo) == strlen(kFoo) + 1sizeof 包括终止的空字符 NUL)。 - vladr

0

后续问题:

您测试过以下内容吗?

static std::string const kFoo = "BAR";

void SaxCallBack(char* sax_string,.....)
{
  if ( sax_string == kFoo)
  {

  }


}

在可读性方面,这是一个净胜利,但我对性能成本一无所知。

作为替代方案,如果你必须自己分派任务,我发现使用类似状态机的方法(带有堆栈)在可读性方面要好得多,并且在性能方面也可能更胜一筹(而不是有大量标签需要切换,只有当前可以满足的标签)。


谢谢,Matthieu。我正在研究一个叫做Ragel的状态机生成器,它可以在多种语言中生成解析器。 - Jacko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接