假设你看到像这样的循环:
for(int i=0;
i<thing.getParent().getObjectModel().getElements(SOME_TYPE).count();
++i)
{
thing.getData().insert(
thing.GetData().Count(),
thing.getParent().getObjectModel().getElements(SOME_TYPE)[i].getName()
);
}
如果这是Java,我可能不会三思而后行。但是在C ++的性能关键部分,它让我想修补它...但是我不知道编译器是否足够聪明以使它变得徒劳无功。
这是一个组成的示例,但它所做的只是将字符串插入容器中。请不要假设这些是STL类型,请概括地考虑以下内容:
换句话说,将它转换为以下内容是值得的(仅在性能方面,而不是可读性):
ElementContainer &source =
thing.getParent().getObjectModel().getElements(SOME_TYPE);
int num = source.count();
Store &destination = thing.getData();
for(int i=0;i<num;++i)
{
destination.insert(thing.GetData().Count(), source[i].getName());
}
请记住,这是一个紧密的循环,每秒呼叫数百万次。我想知道的是,如果所有这些都会在每个循环中产生几个循环或更重要的东西吗?
是的我知道关于“过早优化”的引用。我知道分析很重要。但这是一个关于现代编译器的更普遍的问题,特别是Visual Studio。
答案 0 :(得分:4)
回答这些问题的一般方法是查看生产的装配。使用gcc,这涉及用-c
替换-S
标记。
我自己的规则不是打击编译器。如果要内联某些内容,那么我确保编译器具有执行此类内联所需的所有信息,并且(可能)我尝试使用明确的inline
关键字来促使他这样做。
此外,内联可以节省一些操作码,但会使代码增长,就L1缓存而言,这可能会对性能造成很大影响。
答案 1 :(得分:2)
您提出的所有问题都是特定于编译器的,因此唯一合理的答案是“它取决于”。如果它对您很重要,您应该(一如既往)查看编译器发出的代码并进行一些时序实验。确保你的代码是在所有优化开启的情况下编译的 - 这对像operator[]()
这样的东西有很大的不同,{{1}}通常被实现为内联函数,但不会内联(至少在GCC中),除非你打开优化。
答案 2 :(得分:1)
如果循环是关键的,我只能建议您查看生成的代码。如果允许编译器积极地优化呼叫,则可能不会成为问题。很抱歉这样说,但现代编译器可以非常好地进行优化,我真的建议分析以找到适合您特定情况的最佳解决方案。
答案 3 :(得分:1)
如果方法很小并且可以并且将被内联,那么编译器可以执行与您相同的优化。所以,看看生成的代码并进行比较。
修改:将const方法标记为const
也很重要,例如:在您的示例count()
和getName()
中应该const
让编译器知道这些方法不会改变给定对象的内容。
答案 4 :(得分:1)
作为一项规则,除非在循环执行期间结果会发生变化,否则不应该在“for condition”中拥有所有垃圾。
在循环外使用另一个变量集。这将在读取代码时消除WTF,它不会对性能产生负面影响,并且它将回避功能如何优化的问题。如果这些调用没有得到优化,这也会导致性能提升。
答案 5 :(得分:0)
我认为在这种情况下,您要求编译器执行更多操作,而不仅仅是它可以获得有权访问的编译时信息的范围。因此,在特殊情况下,杂乱的情况可能会被优化掉,但实际上,编译器没有特别好的方法来了解您可能从那长串函数调用中产生什么样的副作用。我认为除非我有基准测试(或反汇编),否则测试会更快。
这是JIT编译器比C ++编译器具有很大优势的情况之一。它原则上可以针对在运行时看到的最常见的情况进行优化,并为此提供优化的字节码(加上检查以确保其落入该情况)。这种事情在多态方法调用中一直被使用,而这些调用实际上并没有被多态地使用;但是,它是否能捕捉到与你的例子一样复杂的东西,我不确定。
对于它的价值,如果速度真的很重要,我也会把它分解为Java。