【问题标题】:Raspberry PI benchmark timings oddities with std::chrono::steady_clockRaspberry PI 基准计时异常与 std::chrono::steady_clock
【发布时间】:2022-10-06 01:29:16
【问题描述】:

我正在尝试使用 std::chrono::steady_clock 在 Raspberry Pi 4 上对一段 DSP 代码进行基准测试,但我得到的结果很奇特。因为 GNU 分析工具不能在 Raspberry Pi 上工作,所以我坚持使用基准测试来评估代码优化,所以这是一件大事。

什么会导致性能在基准程序的执行之间变化 10%,而在程序的同一执行中多次运行相同的测试时保持一致的 +/-1%?

约 6 秒基准测试的结果相差约 10%。但奇怪的是,对于基准测试的特定执行,差异似乎是粘性的。每次运行程序时,我都会连续运行 3 次基准测试,并获得大致相同的结果 +/- 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),使用手动优化的 Neon 内在函数来生成实时音频。执行的大部分(约 90%)是使用手动优化的 neon 内在函数的矩阵和向量算术。数据占用空间约为 13kb(适合 L1 d-cache)。代码足迹未知,但可能不适合 L1 i-cache。大多数代码流水线都很漂亮,因此代码可能会在接近 L1 缓存带宽限制的情况下运行。到目前为止,优化已导致从 ~0.18 x 实时提高到 0.093 x 实时。我认为可能还有大约 15% 的改进可用,但此时时间不准确正在阻碍。被测代码被执行了 3 次,耗时约 0.3 倍,因此实际上需要进一步优化批判的.

已检查的事项:

  • 不是 NEON 对齐问题。所有矩阵、矩阵行和向量都是 16 字节对齐的(在调试编译中使用断言检查)。

  • 不是 CPU 频率问题。 CPU 缩放调控器已设置为performance,所有 CPU 都以 1.8Ghz 运行。

  • 我不认为这与进程之间的缓存竞争有关。当通过 VNC 连接时,HTOP 表示空闲时 CPU 使用率约为 6%,通过 ssh 连接时约为 0.3%(wifi 请求者)。通过 SSH 连接时,模式不会发生显着变化。

  • 我认为它不会因代码运行在哪个 CPU 内核上而有所不同——尽管我只能使用 HTOP 确定代码在特定运行中运行在哪个内核上,这并不完全确定.测试运行似乎偶尔会转移到不同的 CPU 内核,但在大多数情况下,它们似乎在每次执行运行的 3 次测试期间在单个随机选择的内核上运行。

  • 我不认为这是热节流。 CPU温度是非常适中的47C。而且我认为 Raspberry PI 4s 在达到 80C 之前不会热油门。

  • 矢量操作依赖于 GCC 编译器自动矢量化,已正确注释严格声明,并验证产生了最佳的霓虹矢量化(具有比我用霓虹内在函数产生的更好的指令调度)。

  • 不是计时器分辨率问题。对std::chrono::steady_clock::now() 的连续调用会产生 37 到 56ns 之间的增量。

  • 时钟选择没有问题。 stable_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。它基于CPU时钟计数器吗?任何细节表示赞赏。

  • 热节流是否反映在 /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq 中。我认为是,但我不确定。

  • 我显然失踪了某物重要的。

技术细节:

  • 树莓派 4b 8GB
  • Linux raspberrypi 5.15.61-v8+ #1579 SMP PREEMPT Fri Aug 26 11:16:44 BST 2022 aarch64 GNU/Linux
  • gcc 版本 10.2.1 20210110 (Debian 10.2.1-6)
  • 测试在 catch2 测试框架下运行。
  • 您是否检查过数据的对齐方式是否因运行而异。它与缓存或向量大小的对齐方式是否完全不同?
  • @John:我想是的。我的矩阵和向量代码保证矩阵行和向量的 16 字节对齐。 ,并且有一些断言保护矩阵和向量计算,以确保对齐正确。

标签: c++ linux gcc raspberry-pi4 high-resolution-clock


【解决方案1】:

终于确定了问题的根源。这个问题似乎是对 L1 缓存内容的非常温和的竞争,可能来自一些后台系统进程。

性能计数器表现出与基准测试相同的奇怪行为:每次启动测试程序时连续运行 3 次,基准测试结果的差异约为 1%;但每次发布的结果会相差约 10%。

奇怪的是,测试运行之间的性能差异是一致的并且持续了几秒钟。但是考虑到 L1 缓存的干扰是多么小,很难猜测有一百多个正在运行的系统进程会干扰基准测试,以及为什么会出现这种相当不幸的模式,特别是因为它们可以以任何调度程序优先级运行。

性能计数器测量的结果说明了这个问题:具有 2,995 条指令的函数每次迭代平均有约 30 次额外的 L1 数据缓存未命中,导致基准测试结果的差异为 10%。出奇。

我无法猜测哪种系统进程会以在 18 秒内保持一致的速率污染 L1 数据缓存,但在更大的时间尺度上会有所不同。

好消息:被测代码非常接近最优。 (一个 LSTM 单元,具有两个大量乘法,以及大量矢量化 ArcTan 和 Sigmoid 函数调用),它设法使用超过 75% 的可用缓存内存带宽,并且每个时钟周期发出几乎两条指令。呜呼!

测试数据

每次测试代码迭代的性能计数器测量平均值。程序的每次启动都会运行约 6 秒的基准测试 3 次。

运行良好的测试程序:

CpuClk      :           1,694
L1D Access  :           1,244
L1D Miss    :               6
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              12
L2 Miss     :               0

---
CpuClk      :           1,694
L1D Access  :           1,244
L1D Miss    :               6
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              12
L2 Miss     :               0

---
CpuClk      :           1,693
L1D Access  :           1,244
L1D Miss    :               6
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              12
L2 Miss     :               0

糟糕的运行:

CpuClk      :           1,797
L1D Access  :           1,244
L1D Miss    :              37
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              78
L2 Miss     :               0

---
CpuClk      :           1,794
L1D Access  :           1,244
L1D Miss    :              37
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              78
L2 Miss     :               0

---
CpuClk      :           1,797
L1D Access  :           1,244
L1D Miss    :              37
L1I Miss    :               0
Instructions:           2,995
L2 Access   :              78
L2 Miss     :               0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    相关资源
    最近更新 更多