(速度)优化影响意外的代码部分

时间:2013-09-20 14:20:35

标签: c++ performance optimization

我正在对自己的代码进行速度优化。伪代码是这样的:

1. myStructure = constructStructure (param1)
2. processStructure(myStructure)
3. myStructure = constructStructure (param2)
4. processStructure(myStructure)

我专注于优化函数constructStructure(param),因为对函数的两次调用占用了 75%的时间。 我没有触及该功能 processStructure(structure)

我确实获得了一个很好的加速(整个过程现在占据原始时间的 75%),但当我测量各自的操作时间1.-4.时,我得到了意想不到的结果:

      before        after
1.   75.10ms      56.88ms
2.   23.12ms      19.32ms
3.   70.72ms      53.22ms
4.   20.81ms      14.45ms

我在部分2.4.中获得了一个(小但是)显着的加速,但没有改变!这是在1000次运行时测量的然后取平均值。 (我没有计算标准偏差,但我确实运行并显示每个变体的单独时间20-areh次,并且它似乎与平均值一致)。

据我所知,在优化之前和之后产生的结构是相同的,并且在两种情况下程序的最终输出都是相同的。

据我所知,没有(实质性)内存泄漏 - 我在测试运行期间监视系统内存,并且使用的内存没有持续增加。 (我知道这不是一个非常好的方法来测试它,并且挖掘潜在的内存泄漏是我的下一站。此外,我确实在我不需要的时候尽快释放/删除所有保留的内存)

并不是说我对加速感到不满意,但是我甚至无法理解发生了什么!因为这是我将使用了很长一段时间的代码完成它,在代码中有一个神秘并不是一个有吸引力的选择。

是否有人对发生的事情有任何想法如何验证您的建议是否真的如此? PS。我正在使用 C ++


由于实际代码太大而无法放在这里(100行用于生成结构+ 300行来处理它),我可以描述它,以及描述更改:

constructStructure

这是一个构建树结构的函数(不是二叉树)(基于灰度图像像素),每个节点都分配了int属性。

constructStructure函数的参数是Comparator,表示像素强度的排序(第一次是less<int>(),第二次是greater<int>())。

优化过程中的更改是使用std::priority_queue而不是std::multimap来实现结构(如{{3}中所述除了它不与std::pair<int,int>一起使用,而是在std::pair<int,myData>

上使用

我已验证生成的myStructure等效(通过检查生成的树 10 不同图像)从某种意义上说:

  • 生成的树具有相同个节点数
  • 节点中包含的数据是相同
  • 使用std::multimap时,节点中的子节点的顺序与使用std::priority_queue时的不同<(子节点是持有的节点)相同的数据)
  • 结论:树与其拥有的数据等效,其结构直到任何单个父节点内子节点的顺序

processStructure

这是一个以DFS(自下而上)的方式检查构造的树的函数。复杂性仅取决于树中节点数,并仅检查分配给每个节点的属性(不是节点中包含的数据,可用于进一步处理这里没用过。)


最后,经过进一步测试以及我指出的节点顺序之间的差异,问题将是: 树中节点的顺序是否可能产生这一重大变化在性能方面,即使(当使用DFS方法遍历树时),节点内的数据也不会被检查,但每个节点只有一个整数属性?

2 个答案:

答案 0 :(得分:3)

有可能:

  1. 内联:也许,processStructure()和constructStructure()位于包含文件或文件中,您可以在其中调用这些功能。如果是这样,编译器可以内联代码本身,以便在proc-call上保存。所以,这只是组合函数调用的宏优化效果。

  2. 缓存效果:可能,您使用更少的代码或更少的内存,并且在优化之后,某些内容可以适合缓存L1 / L2。

  3. 我建议你为两个程序变量生成汇编文件(gcc的-S选项),并比较汇编代码。

答案 1 :(得分:1)

这是一个猜测,因为我们没有要查看的代码,即使使用代码,也需要仔细研究才能验证,即便如此,它可能在不同的系统上执行完全不同。但是,参考和虚拟内存转换的缓存和位置可能会对此处产生重大的性能影响。

仔细查看处理阶段访问节点的顺序(即按顺序,预订,后订单,随机,等等)。然后考虑相对于将按顺序处理的其他节点可能已经分配了每个节点的位置。如果在访问一个节点之后,下一个节点恰好在内存中非常靠近(特别是在同一个高速缓存行中,但也可能在同一个虚拟内存页面内,因为TLB资源有限),它的访问速度可能比从缓存的角度来看,该节点位于相当随机的位置。缓存执行预取操作,这意味着如果以主要线性方式访问内存,缓存可以隐藏访问主内存的大量延迟。最糟糕的情况是,如果每个节点处于完全不同的高速缓存行中,则在与前一节点没有可辨别关系的位置。关于缓存层次结构和虚拟内存性能还有很多其他内容 - 整本书都写在这个主题上。

我猜你用于构建树的两种不同方法导致在树中分配节点的顺序明显不同,结果是以后遍历树有一种完全不同的内存访问模式,这可能导致显着性能变化。我不确切知道你会怎么做,但如果你可以安排你的所有节点在内存中连续,按照你在处理过程中访问它们的顺序,这几乎是最好的安排,而你可能会看到比现在更快的速度。通常,你必须满足于“足够好”,特别是如果不同的输入会导致数据集显着不同。

valgrind有一个cachegrind模块,可以帮助模拟某个缓存层次结构如何与您的程序一起使用的近似值 - 找到一些这些类型的差异很有用,尽管它不应该是作为一个严肃的性能保证,因为它是一个比真实更简单的缓存模型,并不能解释多任务,内核用户上下文切换等。