gpt4 book ai didi

c - 可预测地对单个函数进行性能分析

转载 作者:行者123 更新时间:2023-12-03 16:14:18 25 4
gpt4 key购买 nike

我需要一种分析数字代码的更好方法。假设我在Cygwin的64位x86上使用GCC,并且我不打算购买商业工具。

情况就是这样。我在一个线程中运行一个函数。除内存访问外,没有代码依赖性或I / O,可能链接了一些数学库。但在大多数情况下,所有这些都是表查找,索引计算和数值处理。我已经缓存对齐堆和堆栈上的所有数组。由于算法的复杂性,循环展开和较长的宏,汇编清单可能变得很长-数千条指令。

我一直在使用Matlab中的tic / toc计时器,bash shell中的time实用程序,或者直接在函数周围使用时间戳计数器(rdtsc)。问题是这样的:时间的差异(可能占运行时间的20%)大于我正在进行的改进的大小,因此我无法知道代码的优劣。更改后。您可能会认为该放弃了。但是我不同意。如果您坚持不懈,那么许多增量改进可以使性能提高两到三倍。

我多次遇到的一个特别令人困扰的问题是我进行了更改,并且性能似乎持续提高了20%。第二天,收益损失了。现在有可能我做了我认为对代码无害的更改,然后完全忘记了它。但是我想知道是否有其他事情正在进行。就像我相信的那样,也许GCC不会产生100%的确定性输出。也许这更简单一些,例如OS将我的过程转移到了更繁忙的内核上。

我已经考虑了以下内容,但我不知道这些想法是否可行或有意义。如果是的话,我想明确说明如何实施解决方案。目标是最小化运行时的差异,以便我可以有意义地比较优化代码的不同版本。


将处理器的内核专用于仅运行例程。
直接控制缓存(加载或清除缓存)。
确保我的dll或可执行文件始终加载到内存中的同一位置。我的想法是,缓存的组关联性可能会与RAM中的代码/数据位置发生交互作用,从而改变每次运行时的性能。
某种周期精确的仿真器工具(非商业)。
是否可以对上下文切换进行一定程度的控制?还是有关系吗?我的想法是上下文切换的时间可能会导致变化,可能是因为在不适当的时间刷新了管道。




过去,通过统计汇编清单中的指令,我在RISC体系结构上取得了成功。当然,这仅在指令数量少的情况下有效。一些编译器(例如TI的C67x的Code Composer)将为您提供详细的分析方法,以使ALU保持忙碌状态。

我还没有发现GCC / GAS生产的装配清单特别有用。启用全面优化后,代码将遍及所有位置。对于在程序集列表中分散的单个代码块,可以有多个位置指令。此外,即使我能理解程序集如何映射回我的原始代码,也不确定在现代x86机器上指令数与性能之间是否存在很大的相关性。

我在尝试使用gcov进行逐行分析方面做得很差,但是由于我构建的GCC版本与MinGW编译器之间不兼容,因此无法正常工作。

您可以做的最后一件事是平均进行许多次试运行,但这需要永远。



编辑(RE:调用堆栈采样)

我的第一个问题是,实际上,我该怎么做?在其中一张幻灯片中,您展示了使用Visual Studio暂停程序。我所拥有的是由GCC编译的DLL,在cygwin中进行了全面优化。然后,由Matlab使用VS2013编译器编译的mex DLL调用此方法。

我使用Matlab的原因是因为我可以轻松地试验不同的参数并可视化结果,而无需编写或编译任何低级代码。此外,我可以将优化的DLL与高级Matlab代码进行比较,以确保优化不会破坏任何内容。

我使用GCC的原因是,与使用Microsoft的编译器相比,我拥有更多的经验。我熟悉许多标志和扩展名。此外,至少在过去,Microsoft不愿维护和更新本机C编译器(C99)。最后,我看到了GCC摆脱了商业编译器的束缚,并且我查看了汇编列表以了解其实际完成方式。因此,我对编译器的实际想法有一些直觉。

现在,关于猜测要解决的问题。这不是真正的问题。这更像是猜测如何解决。在此示例中,就像在数值算法中经常出现的那样,实际上没有I / O(不包括内存)。没有函数调用。几乎没有任何抽象。就像我坐在一块萨兰包装上。我可以看到下面的计算机体系结构,两者之间实际上没有任何关系。如果我重新汇总所有循环,我可能会将代码放在大约一页左右的位置上,并且几乎可以算出最终的汇编指令。然后,我可以对单个核能够执行的理论操作数进行粗略的比较,以了解我有多接近最佳状态。然后的麻烦是,我失去了展开时获得的自动矢量化和指令级并行化能力。展开后,装配清单太长,无法以这种方式进行分析。

关键是该代码实际上没有太多内容。但是,由于编译器和现代计算机体系结构的复杂性令人难以置信,因此即使在此级别上也要进行大量优化。但是我不知道有什么小变化会影响编译代码的输出。让我举几个例子。

第一个有点模糊,但是我敢肯定我已经看过几次了。您进行较小的更改即可获得10%的改进。您进行了另一个小更改,又获得了10%的改进。您撤消第一个更改,然后再获得10%的改进。 ??编译器优化既不是线性的也不是单调的。可能第二个更改需要一个额外的寄存器,这通过强制编译器更改其寄存器分配算法而破坏了第一个更改。也许,第二个优化以某种方式阻碍了编译器执行优化的能力,而该能力是通过撤消第一个优化来解决的。谁知道。除非编译器具有足够的内省性,可以在每个抽象级别上转储其全部分析,否则您将永远无法真正知道最终的汇编是如何完成的。

这是我最近发生的一个更具体的例子。我手动编码AVX内部函数以加快过滤器操作。我以为我可以展开外循环来提高指令级的并行度。我这样做了,结果是代码慢了一倍。发生的事情是没有足够的256位寄存器来处理。因此,编译器将结果临时保存在堆栈中,这会降低性能。

正如我在您提到的this post中提到的那样,最好告诉编译器您想要什么,但不幸的是,您通常别无选择,通常被迫通过猜测和检查来进行优化。

因此,我想我的问题是,在这些情况下(代码在展开之前实际上很小,每个性能的增量变化都很小,并且您正在以非常低的抽象水平进行工作),最好具有“精确的时间”还是调用堆栈采样能更好地告诉我哪个代码更好?

最佳答案

我是对的,您正在做的事情是对要解决的问题进行有根据的猜测,将其修复,然后尝试进行衡量以查看它是否有所不同?

我以不同的方式进行操作,当代码变大时,这种方法特别有效。
而不是猜测(我当然可以),我让程序通过使用this method告诉我如何度过时间。
如果该方法告诉我大约有30%的时间用于某某某事,那么我可以集中精力找到一种更好的方法。
然后,我可以运行它并对其进行计时。
我不需要很多精度。
如果更好,那就太好了。
如果情况更糟,我可以撤消更改。
如果是相同的话,我可以说:“哦,也许它并没有节省很多,但是让我们再做一次以找到另一个问题,”

我不用担心
如果有一种方法可以加快程序的速度,这将查明它。
通常,问题不只是一个简单的陈述,例如“行或例程X花费Y%的时间”,而是“这样做的原因是在某些情况下是Z”,而实际的解决方法可能在其他地方。
修复问题后,可以再次执行该过程,因为以前较小的另一个问题现在更大了(以百分比表示,因为通过修复第一个问题已减少了总数)。
重复是关键,因为每个加速因子都会乘以所有先前的因子,例如复利。

当程序不再指出我可以解决的问题时,我可以确定它几乎是最佳的,或者至少没有其他人可以击败它。

而且在此过程中,我根本不需要非常精确地测量时间。
然后,如果我想在PowerPoint中吹牛,也许我会做多个计时以获得较小的标准误差,但是即使那样,人们真正关心的还是整体加速因素,而不是精度。

关于c - 可预测地对单个函数进行性能分析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40790903/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com