调用"list<T>::end()"不是效率低下吗?

Isn't calling `list<T>::end()` inefficient?

本文关键字:效率 end lt list gt 调用      更新时间:2023-10-16

在一本C++编程书中,我看到了std::list迭代器的以下内容:

for (iterator = list.start(); iterator != list.end(); iterator++)

一直打电话给list.end()不是效率低下吗?将结尾保存到另一个变量会更好,还是C++编译器(即 g++(会自动处理这个问题?

list::end()应该具有恒定的时间复杂度,特别是对于链表,这意味着它可能非常有效。

如果您的算法允许,存储值可能会稍微高效一些(同样,对于特别链表,差异不太可能很大(。

哦,请阅读史蒂夫杰索普关于自己测试效率的答案!

调用std::list<T>::end()不太可能是一个大的效率问题,可能只读取单个值。但是,您会给编译器一个提示,表明它不会通过将它存储为变量来更改。对于其他容器,除了读取基址之外,还可能涉及计算,这更复杂一些。仍然没有什么戏剧性的,但可能值得避免。

但请注意,它也可能改变循环的语义:如果循环的主体可能附加元素,则前一端可能会移动。有趣的是,我在标准中没有找到任何具体要求,说明在将元素插入容器时std::list<T>::end()是否会更改(我可以想象它确实会更改的实现以及一些不会更改的实现;很可能它不会更改,不过(。如果要在修改列表时获得有保证的行为,则很可能在每次迭代中调用list.end()

顺便说一句,我有一个更大的性能问题,那就是使用iterator++而不是++iterator,尤其是这是作者在书中使用的。尽管如此,这是一个微优化,就像存储list.end()的结果一样,但做起来很便宜。

一般来说,不,它不是低效的。 end()通常是一个内联函数,编译器将生成良好的代码来执行它所做的任何事情。更重要的是,与什么相比效率低下?是的,您可以添加代码来创建变量来保存结果,这可能比简单地调用 end() 快一点,也可能不会快一点。这样的变化似乎不太可能产生足够大的速度差异,将太慢的程序变成满足要求的程序。

这不太可能有任何区别。

标准容器函数是内联的,因此应该没有明显的函数调用开销。剩下的就是优化器是否足够聪明,以避免不必要的开销,而这些开销对于执行比较并不是绝对必要的。例如:它是否实际上创建了一个临时list::iterator对象,填写其当前位置字段,然后读回该字段,或者比较最终只是作为iterator中的值与列表头部的值之间的指针比较?

即使有一些不必要的开销,与增加迭代器相比,它也可能可以忽略不计,与循环体相比甚至可以忽略不计。

你可以测试它,这比猜测更可靠。记住要启用优化——在没有优化的情况下测试性能有点像说,如果布莱克从热身跑道到公共汽车走得更快,布莱克一定比博尔特快。

实际上,对于 STL 容器,container::end()非常便宜。事实上,C++标准要求多个类的几种方法(如果不是全部(的算法复杂性,并且container::end()始终是恒定时间。

此外,编译器可以自由地内联这些方法,基本上消除了它可能具有的任何开销。我想不出除了存储列表之外,没有其他方法可以在恒定时间内获取列表的末尾,因此您的list.end()调用可能最终成为字段访问,这在 x86 平台上并不比将其存储在堆栈上更昂贵。

您的里程可能因其他系列而异,但可以肯定的是,list.end()最终不会成为您的瓶颈。

如果您需要微优化,是的。

一般来说,呼叫list.end()不会对性能造成重大影响,也可能不会成为问题。它可能每次调用都返回相同的值,可以内联,等等。虽然不,但确实需要一些小时间。

如果你绝对需要速度,你想使用for (iterator = list.start(), end = list.end; iteration != end; ++iterator).这会缓存结束迭代器(并执行预 inc(,并且不应有重复调用。

第二种类型通常是不必要的,但如果.end()昂贵或循环非常大,则可能很有用。

虽然过早的优化是邪恶的,但好习惯不是。如果您不希望循环终止条件发生变化,即您没有更改容器,则可以使用此模式:

for (mylist::iterator it = alist.begin(), finish = alist.end();  it != finish;  ++it)

如果编译器无法确定容器未更改,则不太可能为你进行此优化。

请注意,这不太可能产生可测量的时间差异,但不会造成伤害。

std::list上缓存end()的一个很好的理由是它可以防止您犯以下错误:

for (iterator = list.rstart(), end = list.rend(); iterator != end; iterator++) {
    // modify list

当您在 std::list 中进行修改时,任何迭代器都不会失效,但rend不是哨兵(它指向底层列表的第一个元素(,这意味着如果您附加到反向列表的末尾(也称为附加到未反向列表的开头(,它将不再是列表的末尾。