不可靠的 Android Jetpack 基准测试库测量结果?

Posted

技术标签:

【中文标题】不可靠的 Android Jetpack 基准测试库测量结果?【英文标题】:Unreliable Android Jetpack benchmarking library measurement results? 【发布时间】:2020-07-08 06:10:12 【问题描述】:

我在不同的非root设备上使用android Jetpack benchmarking framework进行一些琐碎的计算:

    @Test
    fun measurePureCalculation() 
        benchmarkRule.measureRepeated 
            val value = 1 * 2 * 3 * 4 * 5
            println(value)
        
    

如果我在完全相同的设备上本地运行 10 次,我会看到不同的结果:

benchmark:        13,831 ns Benchmark.measurePureCalculation
benchmark:        13,688 ns Benchmark.measurePureCalculation
benchmark:        13,773 ns Benchmark.measurePureCalculation
benchmark:        13,933 ns Benchmark.measurePureCalculation
benchmark:        13,823 ns Benchmark.measurePureCalculation
benchmark:        13,581 ns Benchmark.measurePureCalculation
benchmark:        13,930 ns Benchmark.measurePureCalculation
benchmark:        14,008 ns Benchmark.measurePureCalculation
benchmark:        13,727 ns Benchmark.measurePureCalculation
benchmark:        13,995 ns Benchmark.measurePureCalculation

在上面的这个测量中,它给出了测量误差 ((max - min) / max) = ((14008 - 13581 / 14008) = ~3%。

一般来说,它似乎给出了非常不稳定的结果,根据测量的代码可能会有 8-13% 的差异(来自生成的基准 JSON 文件的一部分 - 在测试场使用真实设备进行测试):

"median": 7473,
...
"median": 7222,
...
"median": 8065,
...
"median": 7409,
...
"median": 6926,

此处(上图)测量误差为 (8065 - 6926)/8065 = ~14%。

这让我觉得测量结果不够可靠,因为它无助于理解代码是变好还是变坏,或者只是测量不准确。

我明白我没有锁定 CPU 频率,因为在非 root 设备上缺乏权限,但我希望它能够工作(通过框架 presentation 中提到的其他方式)。

我们已经在不同设备上与几个开发人员进行了尝试(显然是比较了相同模型的结果),甚至是使用真实设备(例如 TestObject)的测试场。所有设置都是默认设置的(发布版本,默认基准测试策略)。使用的版本是:

类路径“androidx.benchmark:benchmark-gradle-plugin:1.0.0”

你有同样的经历吗?除了没有锁定 CPU 频率之外,另一个 [可能的] 原因是什么?关于可以进一步检查/调查什么的任何建议?如果我们在版本上改进代码,一般有哪些选择可以理解?可以在这里使用 Firebase 测试实验室吗?

更新:

正如 Chris 指出的那样,它确实在编译时优化为值 120(完全忘记了,从 bytecode 逆向工程),所以不清楚本质上测量的是什么:

【问题讨论】:

【参考方案1】:

这里是库开发者,感谢您提供详细的问题 -

首先,您是否尝试过更简单的方法来衡量这种基线稳定性?一般来说,如果你的工作是一致的,那么稳定性会更高,特别是如果它是单线程的。我预计(但尚未检查)println() 可能由于下面的缓冲而导致性能不稳定,这可能导致循环之间的工作量不一致。您也可以尝试通过 measureRepeated repeat(100) ... 来解决此问题,但不能保证会奏效。

如果您正在寻找一小部分 CPU 工作作为最佳稳定性测量的基准,我建议您使用类似 this 的方法。这是 Benchmark 库在内部测量以检测热节流的一小部分矩阵数学。由于它是对平台代码的调用,因此保证不会被优化(与您正在执行的乘法不同,它将在编译时折叠为 println(120))。

此外,请尝试查看minmedian,看看这是否能让您更加稳定。一般来说,干扰会使事情变得更慢,而不是更快,因为在热身结束时,你的时钟可能会尽可能快地上升。

稳定性越高,您可以捕获的回归越小,而且我们看到 CI 的稳定性范围很广,具体取决于工作负载。我们不断地在库和基准测试本身上工作,以获得更好的稳定性。不过,即使是低稳定性基准也可以用于发现大的意外回归。

诚然,在我们自己在 Jetpack 中使用的基准测试中,我们所有的基准测试都在时钟锁定的根设备上运行。但是,您可以自己查看所有结果和稳定性,例如,一些 trivial synchronization benchmarks 的结果为 here,今天从最小值到最大值有大约 3% 的差异,忽略了几个异常值:

在实践中,我发现基准测试的内容对于稳定性而言比锁定时钟更重要,尽管我真的希望我们最终能够在非 root 用户上进行测试。

请注意,我们还提供了followup presentation at Droidcon TO,关于如何在 CI 中使用这些数据,以及处理周期性不稳定性。

【讨论】:

感谢您的详细解释。实际上,我们从测量一些更复杂的代码开始,发现它不够准确,最终尝试了我发布的 sn-p 来检查。我'一直在考虑“发布”优化和println 缓冲,但它感觉像是一个自然的用例,所以我预计会有或多或少可靠的结果(3% 听起来可以接受,但 14%)。我想目前还不清楚正常情况下可以预期的准确性。在 IO19 宣布之后,我真的很惊讶它实际上是多么困难,所以预期的热节流以某种方式解决了。 感谢您指出可能的方向,在有根设备上进行测试,看看会发生什么

以上是关于不可靠的 Android Jetpack 基准测试库测量结果?的主要内容,如果未能解决你的问题,请参考以下文章

第02章 MySQL基准测试

Android kotlin 系列讲解(进阶篇)Jetpack系列之LiveData

完整体验 Jetpack WorkManager 及场景探讨

高性能MySQL读书笔记--MySQL基准测试

android genymotion vs 模拟器

WebSockets、UDP 和基准测试