我正在尝试使用std::chrono::steady_clock在Raspberry Pi 4上对一段DSP代码进行基准测试,但我得到的结果很奇怪。因为GNU评测工具在Raspberry Pi上不起作用,所以我不得不用基准测试来评估代码优化,所以这是一件相当大的事情。
什么会导致性能指标评测程序执行之间的性能差异为10%,而在程序的同一次执行中多次运行同一测试时,性能保持一致(+/- 1%)?
一个大约6秒的基准测试的结果相差大约10%。但奇怪的是,这种差异似乎对基准测试的特定执行具有粘性。每次运行程序时,我连续运行三次基准测试,得到的结果大致相同(+/-1%)。但当我重新运行程序时,三个基准测试的结果与前一次运行的结果相差+/- 10%。但是在新的运行中三个结果中的每一个都是+/-1%。
例如:
Run 1:
9:21:37. Performance: 0.0912333 x realtime
9:21:42. Performance: 0.0910667 x realtime
9:21:47. Performance: 0.0910667 x realtime
Run 2:
9:20:15. Performance: 0.106667 x realtime
9:20:21. Performance: 0.1062 x realtime
9:20:28. Performance: 0.106117 x realtime
每次运行的结果大致在这两个极端之间随机变化,但奇怪的是,每次运行程序时,三次测试的结果都一致,误差为+/- 1%。
我是一个有经验的程序员,所以我知道基准测试会有一些变化。但是~10%的变化对于我正在尝试做的事情是不可行的。而且我也无法提出一个合理的理论来解释为什么变化会随着调用的不同而变化。
测试中的代码是机器学习算法(LSTM-〉Dense),使用手动优化的氖内部函数来生成实时音频。(~90%)是使用手动优化的氖内部函数的矩阵和向量运算。数据占用空间约为13 kb(很适合L1 d-cache)。代码占用空间未知,但可能不适合L1 i-cache。大多数代码流水线都很漂亮,因此代码可能会在接近L1缓存带宽限制的情况下运行。到目前为止,优化已经使实时性能从~0.18倍提高到0.093倍。我认为可能还有~15%的提高,但是时间的不准确性在这一点上成为了障碍。2测试中的代码被执行了三次,花费了大约0.3倍的实时时间,所以进一步的优化实际上是至关重要的。
已检查的事项:
- 不是氖对齐问题。所有矩阵、矩阵行和向量都是16字节对齐的(在调试编译中用Assert检查)。
- 不是CPU频率问题。CPU缩放调控器已设置为
performance
,并且所有CPU均以1.8Ghz运行。 - 我不认为这与进程之间的缓存竞争有关。HTOP表明,当通过VNC连接时,空闲时CPU使用率约为6%,当通过ssh连接时,CPU使用率约为0.3%(wifi请求者)。当通过SSH连接时,这种模式没有明显变化。
- 我不认为它会随着代码运行在哪个CPU内核上而变化--尽管我只能使用HTOP来确定代码在特定的运行中运行在哪个内核上,这并不是完全确定的。测试运行似乎偶尔会转移到不同的CPU内核上,但在大多数情况下,它们似乎在每次执行运行3个测试的持续时间内运行在单个随机选择的内核上。
- 我不认为这是热节流。CPU的温度是一个非常温和的47摄氏度。我不认为树莓PI 4的热节流,直到他们达到80摄氏度。
- 矢量化操作依赖于GCC编译器自动矢量化,该自动矢量化已使用restrict声明进行了正确注解,并已验证可生成最佳的氖矢量化(与使用Neon内部函数生成的指令调度相比,指令调度更好)。
- 不是计时器分辨率问题。连续调用
std::chrono::steady_clock::now()
会产生37到56 ns之间的增量。 - 选择时钟不是问题。steady_clock、system_clock和high_resolution_clock都表现出相同的行为。
已验证CPU频率:
$ cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
performance
performance
performance
performance
$ cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
1800000
1800000
1800000
1800000
我不知道你能不能帮上忙:
- std::chrono::steady_clock是如何在Raspberry Pi上实现的。它是基于CPU时钟计数器的吗?了解详细信息。
- 散热限制是否反映在/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq中。我认为是,但我不确定。
- 我显然错过了一些重要的东西。
技术细节:
- 树莓派4 b 8 GB
- Linux raspberrypi 5.15.61-v8+ #1579 SMP抢占星期五8月26日11:16:44英国夏令时2022 aarch 64 GNU/Linux
- (Debian 10.2.1-6)软件开发工具
- 测试在catch 2测试框架下运行。
1条答案
按热度按时间oaxa6hgo1#
最后确定了问题的根源。该问题似乎是对L1缓存内容的非常轻微的竞争,可能来自一些后台系统进程。
性能计数器表现出与基准测试相同的奇怪行为:每次启动测试程序时,连续运行3次,基准测试结果的差异约为1%;但不同发射的结果相差约10%。
奇怪的是,测试运行之间的性能差异是一致的,并且持续了几秒钟。但是,考虑到L1缓存的干扰是多么微小,很难猜测一百多个正在运行的系统进程中的哪些进程在干扰基准测试,以及为什么会出现这种相当不幸的模式,特别是因为它们可能以任何调度程序优先级运行。
性能计数器测量的结果说明了这个问题:在一个包含2,995条指令的函数中,每次迭代平均会有30次额外的L1数据缓存未命中,这会导致基准测试结果出现10%的偏差。
我无法猜测哪种系统进程会以在18秒内保持一致的速率污染L1数据缓存,但在更大的时间尺度上会有所不同。
好消息是:测试中的代码非常接近最优。(一个LSTM单元有两个实质性的乘法,以及大量的矢量化ArcTan和Sigmoid函数调用),它管理使用超过75%的可用高速缓存带宽,并且每个时钟周期发出几乎两条指令。
测试数据
性能计数器测量值是测试代码每次迭代的平均值。程序的每次启动都会运行三次约六秒的基准测试。
测试程序的良好运行:
运行不佳: