您使用哪些工具来确定代码的效率?您是否使用运行大量测试的本地应用程序或某些商业产品?您是否使用自己的知识来测试代码的某些区域,或者使用一些工具来分析代码的弱点?
9 回答
插入:让我们看看“统计意义”。
假设某处有函数调用指令。你不一定能看到它——一个类,一个宏,或者编译器,可能已经把它滑进去了。附近还有对同一个函数的其他调用,但是这个调用是在一个循环中,或者它的参数是这样的使这个电话需要很长时间。事实上,如此多的时间,如果可以使此调用花费零时间,那么总执行时间将减少一定数量,例如 90%。(不可能?一点也不。)时间会精确吗?不会。调用图会查明它吗?不。电话计数会查明吗?不,因为问题不在函数级别,而是在调用指令级别。
不知何故,程序在某个时间点随机停止,并检查其状态。如果指令可以“归零”,它会在 90% 的时间内停止吗?当然 - 以 90% 的概率,指令将被精确定位在堆栈上,等待其“工作”完成。
事实上,如果你随机停止它 20 次,该指令将平均在堆栈上 18 次,标准偏差为 +/- 1.3 次。
这有统计学意义吗?你打赌它是。
您需要大量样品吗?你打赌你没有。
假设百分比很小,例如 10% 或 5%。同样的原则也适用。
事实上,无论抽取多少样本,任何大于 1 个样本的指令都具有统计显着性,并且是“热点”、“瓶颈”或任何你想称之为的指令。如果您可以删除它,减少调用它,或者以某种方式减少它,它将节省大量时间。(有些你不能,比如“call _main”,但有些你可以。你只需要找到它们。)
当然,我的代码永远不会这么愚蠢,不是吗?那么,证明它。
好的,现在回到故事上来。. .
原始答案:我早在很久以前就听说过分析器,我认为它们一定很整洁,但我无法访问它们/它。我正在研究一个嵌入式处理器(一个 8086 intel 芯片),它似乎需要很长时间才能在显示屏上绘制一些浮点数。硬件人员建议从他们的大量提供 - 添加计时器芯片,这样我就可以看到事情需要多长时间。然而,一个周末,我用一个英特尔“Blue Box”在线仿真器启动它并让它运行起来。虽然它很慢,但我想知道“它到底在做什么?”。所以我只是停下来找出答案。PC 在浮点库中(没有 FP 芯片)。这并不奇怪,因为它正在绘制浮点数,但我想知道更多。所以我(费力地)读取十六进制内存以跟踪调用堆栈。你猜怎么了?它在取要绘制的数字,除以10,转换为整数,转换回浮点数,减法等等,只是为了得到下一个数字来绘画。不用说,有更好的方法可以做到这一点,从而加速了大约 10 倍。 这是用一 (1) 个样本发现的!
另一次,在 68K 芯片上,出现了一些缓慢。同样,分析器不可用,但调试器“adb”可用,所以虽然它很慢,但我停止了几次。PC 在数学库中,实际上在 32 位整数乘法例程中。查找堆栈,我发现了以下代码:
struct {...} a[...];
int i;
for (i = 0; i < ...; ++i){ ... a[i] ... }
那里没有乘法的呼唤——这是怎么回事?事实证明,对于a[i]
,编译器必须乘以i
数组元素的大小。由于i
是 32 位(在该编译器中),它会生成对 32 位乘法例程的调用,并且堆栈会精确定位该调用指令。通过声明i
as short
,循环速度提高了两倍!
重点是什么?如果您在程序运行缓慢时随机抽取样本,PC 会告诉您它在做什么,但调用堆栈会告诉您原因,并直接引导您解决问题。现在,除非问题真的很严重,否则您可能需要采集多个样本。任何出现在 >1 个样本上的陈述都是您可以怀疑的。请注意,它甚至可以精确定位语句、指令,而不是像函数这样的大块代码。这种技术可能“快速而肮脏”,但非常有效。
补充:如果你反复这样做,你可以在同一个软件中解决一个又一个问题。例如,如果您获得 3 倍的加速,以前可能无关紧要的小性能问题现在会消耗 3 倍的剩余时间。这使它们更容易被样本击中。您可能必须添加一个临时的外部循环以使其运行足够长的时间进行采样。通过这种方式,我看到了超过 40 倍的复合加速因子。
分析器对于查看您花费最多时间在哪些代码上非常有用。市面上有许多分析工具,通常它们特定于您所在的平台/开发环境。
对于小情况,我在代码中使用了简单的计时器(操作结束时的系统时间 - 操作开始时的系统时间)。
一条重要规则:永远不要假设您刚刚进行的性能优化实际上会运行得更快。随时验证!
我确实使用工具来完成这项工作。否则我发现自己很难做到......(这是我的意见,我实际上从未尝试过)
在 Linux 上,我使用Valgrind,它提供了一些有用的工具来分析你的代码。至于 Valgrind 主页:
它在以下平台上运行:X86/Linux、AMD64/Linux、PPC32/Linux、PPC64/Linux。
..而且它是免费的(就像免费啤酒一样)并且是开源的。
虽然我没有过多地使用它们,但在另一个平台上,您可以使用Purify/Quantify(IBM 产品)。它们是商业工具。正如 Will 所报告的,它们是在 PurifyPlus 包中提供的,Quantify 将帮助您分析您的应用程序。
这就是我用过的所有...... :-)
老实说,我使用NUnit。如果我的代码花费的时间太长或似乎无法扩展,我会编写一个测试来模拟应用程序中性能不佳的部分。然后我看看在代码中进行强度替换以减少运行时间并验证我没有破坏任何东西。
如果这仍然不能满足您的需求,那么您需要找到并应用分析器,但您至少会有一个测试用例,您可以在无需打开/加载应用程序的情况下尝试您的假设,跟踪对元素的调用与手头的任务无关的应用程序。
我使用 Valgrind 及其工具 Callgrind。这是一个很棒的工具。Valgrind 基本上是一个虚拟机:
Valgrind 本质上是一个使用即时 (JIT) 编译技术的虚拟机,包括动态重新编译。原始程序中的任何内容都不会直接在主机处理器上运行。相反,Valgrind 首先将程序转换为一种临时的、更简单的形式,称为中间表示 (IR),它是一种与处理器无关、基于 SSA 的形式。转换之后,在 Valgrind 将 IR 转换回机器代码并让主机处理器运行它之前,工具(见下文)可以自由地对 IR 进行任何转换。即使它可以使用动态转换(即主机和目标处理器来自不同的体系结构),它也不能。Valgrind 重新编译二进制代码以在相同架构的主机和目标(或模拟)CPU 上运行。
Callgrind 是一个基于此的分析器。主要好处是您不必运行应用程序数小时即可获得可靠的结果。几秒钟就足够了,因为 Callgrind 是一个非探测分析器,
另一个基于 Valgrind 的工具是 Massif。我用它来分析堆内存使用情况。它工作得很好,它为您提供内存使用情况的快照——详细信息 WHAT 拥有多少内存百分比,以及谁把它放在那里。
另一种 Valgrind 工具——DRD(和 Helgrind)。我使用它们来跟踪我的代码中的死锁和数据竞争,以及线程 API 滥用。
这个问题是模棱两可的。从运行时或内存的角度来看高效?运行特定代码的上下文、应用程序的架构以及可能被抛出的数据也是所有因素。
顺便说一句,有人提到净化;还有它的姊妹产品 Quantify。
对于 .NET,我会推荐NDepend。