C ++在for循环中使用预先计算的限制器

时间:2010-03-01 17:44:16

标签: c++

像PHP这样的脚本语言有这样的for循环会是一个非常糟糕的主意:

string s("ABCDEFG");
int i;
for( i = 0; i < s.length(); i ++ )
{
   cout << s[ i ];
}

这是一个例子,我没有建立这样的程序。 (对于那些觉得他们必须告诉我为什么这段代码&lt; 在这里插入不好的东西的人&gt;)

如果将此C ++示例转换为类似的PHP脚本,则会在每个循环周期中计算字符串的长度。这会在现实脚本中造成巨大的性能损失。

我认为这同样适用于C ++程序但是当我看一下教程,几个开源库和其他代码时,我发现循环的限制器没有预先计算。

  • 我应该预先计算字符串s的长度吗?
  • 为什么限制器总是不会被预先计算出来? (在教程和示例中看到)
  • 编译器是否进行了某种优化?

16 个答案:

答案 0 :(得分:11)

这都是相对的。

PHP被解释,但如果s.length落入PHP解释器的编译部分,它将不会很慢。但即使它很慢,s[i]花费的时间又会怎样,cout <<花费的时间呢?

在淹没其他东西的同时,很容易专注于循环开销。

就像你用C ++写的那样,并且cout正在写入控制台,你知道什么会占主导地位吗? cout会远远地,因为那个看上去无辜的<<运算符会调用大量的库代码和系统例程。

答案 1 :(得分:4)

你应该学会证明更简单的代码。试着说服自己,迟早你会将string :: length实现替换为更优化的实现。 (即使你的项目很可能会错过所有截止日期,并且优化字符串::长度将是你问题中最少的。)这种思维将帮助你专注于真正重要的事情,即使它并不总是那么容易......

答案 2 :(得分:3)

这取决于string的实施方式。

在null终止字符串时,您必须在每次迭代时计算大小。

std::string是一个容器,在<(1)时间内返回 它取决于(再次)实现。

答案 3 :(得分:2)

如果他能够确定其值不会改变,那么优化器确实可以优化对length的调用 - 然而,如果你预先计算它,你就是安全的(在许多情况下)但是,优化是不可能的,因为编译器不清楚条件变量是否可以在循环期间改变)。

在许多情况下,它并不重要,因为所讨论的循环与性能无关。与for(int i=0; i < somewhat(); ++i)相比,使用经典for(int i=0,end=somewhat(); i < end; ++i输入的工作量更少,更易于阅读。

请注意,C ++编译器通常会内联小函数,例如length(通常会从字符串对象中检索预先计算的长度)。解释的脚本语言通常需要对函数调用进行字典查找,因此对于C ++,每次循环迭代一次冗余校验的相对高估可能要小得多。

答案 4 :(得分:2)

我不知道php,但我可以告诉c++做什么。 考虑:

std::string s("Rajendra");
for (unsigned int i = 0; i < s.length(); i++)
{
    std::cout << s[i] << std::endl;
}

如果您要查找length()的定义(右键单击length()并单击“转到定义”)或者如果您使用的是Visual Assist X,则将光标放在{{1然后按length(),您会看到以下内容:

Alt+G

其中size_type __CLR_OR_THIS_CALL length() const { // return length of sequence return (_Mysize); } 的类型为_Mysize,这清楚地表明字符串的长度已预先计算,并且每次调用int时仅返回存储的值。

然而,IMPO(在我个人看来),这种编码风格很糟糕,应该最好避免。我更愿意关注:

length()

这样,您将节省调用std::string s("Rajendra"); int len = s.length(); for (unsigned int i = 0; i < len; i++) { std::cout << s[i] << std::endl; } 函数的开销等于字符串次数的长度,这样可以节省堆栈帧的推送和弹出。当你的弦很大时,这可能非常昂贵 希望有所帮助。

答案 5 :(得分:1)

  1. 可能。
  2. 为了便于阅读。
  3. 有时。这取决于检测到循环内长度不会改变的程度。

答案 6 :(得分:1)

你是对的,通常会在每次循环迭代时评估s.length()。你最好写作:

size_t len = s.length();
for (size_t i = 0; i < len; ++i) {
   ...
}

而不是上述。也就是说,对于只有几次迭代的循环,调用length()的频率并不重要。

答案 7 :(得分:1)

简短回答,因为在某些情况下,您希望每次调用它。

其他人的解释:http://bytes.com/topic/c/answers/212351-loop-condition-evaluation

答案 8 :(得分:0)

嗯 - 因为这是一种非常常见的情况,大多数编译器都会预先计算价值。特别是在循环遍历数组和非常常见的类型时 - 字符串可能就是其中之一。

此外,引入一个额外的变量可能会破坏其他一些循环优化 - 它实际上取决于您使用的编译器,并且可能会从版本更改为版本。
因此,在某些情况下,“优化”可能会适得其反。

如果代码是非真实的“热点”,其中每个性能滴答都很重要,你应该像你一样写:没有“手动”预先计算。

在编写代码时,可读性也非常重要!
只有经过深入分析后才能非常仔细地进行优化!

答案 9 :(得分:0)

std::string.length()返回存储在容器中的固定变量。它已经预先计算了

答案 10 :(得分:0)

只有在 知道 时,您才能预先计算字符串的长度。字符串在循环内不会发生变化。

我不知道为什么在教程中这样做。一些猜测:
1)为了让你养成习惯,这样当你改变循环中字符串的值时你就不会受到冲击 2)因为它更容易理解。

是的,如果优化器可以确定字符串是否不会改变

,那么优化器会尝试改进这一点

答案 11 :(得分:0)

编译器可能能够保存调用的结果并优化掉所有额外的函数调用,但它可能不会。但是,函数调用的成本将非常低,因为它所要做的就是返回一个int。最重要的是,它很有可能被内联,完全消除了函数调用的成本。

但是,如果您真的在意,您应该对代码进行分析,看看预先计算该值是否会使其更快。但是,无论如何只选择预先计算它也不会有什么坏处。它不会花费你任何东西。但在大多数情况下,赔率并不重要。有一些容器 - 比如列表 - 其中size()可能不是O(1)操作,然后预先计算将是真正的好主意,但对于大多数它可能并不重要 - 特别是如果你的循环内容足以压倒这种高效函数调用的成本。对于std :: string,它应该是O(1),并且可能可以被优化掉,但你必须进行测试以确保 - 当然还有你编译的优化级别之类的东西at会改变结果。

预先计算更安全,但通常没有必要。

答案 12 :(得分:0)

在这种特殊情况下,std::string.length() 通常(但不是necessarily)常量操作,通常效率很高。

对于一般情况,循环终止条件可以是任何表达式,而不仅仅是循环索引变量的比较(实际上,C / C ++不识别任何特定索引,只是初始化表达式,循环测试表达式和循环计数器表达式(每次都执行它).C / C ++ for循环基本上是do/while复合语句的语法糖。

答案 13 :(得分:0)

std::sting::length()返回预先计算的值。每次调用方法size()时,其他stl容器会重新计算其大小 例如

  • std::list::size()重新计算尺寸和
  • std::vector::size()返回一个 预先计算的值

这取决于如何实现容器的内部存储。 std::vector是一个容量为2 ^ n的数组,std::list是一个链接列表。

答案 14 :(得分:0)

只是为了获取信息,在我的计算机上,g ++ 4.4.2,使用-O3,使用给定的代码片段,函数std::string::length() const被调用8次。

我同意这是预先计算的,并且该功能很可能被内联。知道何时使用自己的函数/类仍然非常有趣。

答案 15 :(得分:0)

如果你的程序一直对字符串执行很多操作,比如复制字符串(使用memcpy),那么缓存字符串的长度是有意义的。

我在开源代码中看到的一个很好的例子是redis

sds.h(简单动态字符串)中查看sdshdr结构:

struct sdshdr {
    long len;
    long free;
    char buf[];
}; 

正如您所看到的,它缓存了字符数组 buf 的长度(在 len 字段中)以及可用的可用空间(在免费字段)在空字符之后。

因此分配给 buf 的总内存为

len + free

如果需要修改buf并且它适合已经可用的空间,这将保存realloc。