迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 >

JavaScript 基准测试

作者:迹忆客 最近更新:2023/01/10 浏览次数:

编写 JavaScript 基准测试并不像看起来那么简单。 即使不涉及潜在的跨浏览器问题,也有很多陷阱——需要注意。

本文将阐明编写和运行 JavaScript 基准测试的各种问题。


基准测试模式

有很多方法可以在 JavaScript 片段上运行基准测试来测试它们的性能。 最常见的模式如下:

模式 A

var totalTime;
var start = new Date;
var iterations = 6;
while (iterations--) {
    // Code snippet goes here.
}
// `totalTime` is the number of milliseconds it took to execute the code snippet 6 times.
totalTime = new Date - start;

这会将要测试的代码置于一个循环中,并执行预定义的次数(在本例中为 6)。 之后,从结束日期中减去开始日期以获得执行操作所花费的时间。

模式 A 用于流行的 SlickSpeed、Taskspeed、SunSpider 和 Kraken 基准套件。

问题

随着浏览器和设备变得越来越快,使用固定迭代计数的基准测试更有可能产生 0 毫秒的结果,这是不可用的。

模式 B

另一种方法是计算在一段时间内执行了多少操作。 这样做的好处是不需要我们像前面的示例那样精确定位迭代次数。

var hz;
var period;
var startTime = new Date;
var runs = 0;
do {
    // Code snippet goes here.
    runs++;
    totalTime = new Date - startTime;
} while (totalTime < 1000);

// Convert milliseconds to seconds.
totalTime /= 1000;

// period → how long each operation takes
period = totalTime / runs;

// hz → the number of operations per second.
hz = 1 / period;

// This can be shortened to:
// hz = (runs * 1000) / totalTime;

此片段执行测试代码大约一秒钟,即直到 totalTime 大于或等于 1000 毫秒。

模式 B 用于 Dromaeo 和 V8 Benchmark Suite。

问题

进行基准测试时,由于垃圾收集、引擎优化和其他后台进程,每次测试的结果都会有所不同。 由于存在这种差异,基准测试应运行多次以获得平均结果。 V8 Suite 只运行一次每个基准测试。 Dromaeo 将每个基准测试运行五次,但可以做更多的工作以减少误差。 一种方法是将基准运行的最短时间从 1000 毫秒降低到 50 毫秒,假设计时器没有错误,从而允许有更多时间重复运行。

模式 C

JSLitmus 是围绕这两种模式的组合构建的。 它使用模式 A 循环测试 n 次,但使用自适应测试周期动态增加 n ,模式 B,直到达到最小测试时间。

问题

JSLitmus 避免了模式 A 的问题,但分享了模式 B 的问题。为了提高结果的准确性,JSLitmus 通过采用最快的 3 次空测试运行并从每个基准测试结果中减去结果来校准结果。 不幸的是,这种技术——虽然旨在消除间接成本——实际上混淆了最终结果,因为“三者中最好的”不是统计上有效的方法。 即使 JSLitmus 多次运行基准测试并从基准测试结果平均值中减去校准平均值,最终结果的误差范围增加也会吞噬任何提高准确性的希望。

模式 D

模式 ABC 的缺点可以通过使用函数编译和循环展开来避免。

function test() {
    x == y;
}

while (iterations--) {
    test();
}

// This would compile to:

var hz;
var startTime = new Date;

x == y;
x == y;
x == y;
x == y;
x == y;
// …

hz = (runs * 1000) / (new Date - startTime);

此模式编译展开的测试以避免循环和校准。

问题

但是,它也有缺点。 像这样编译函数会大大增加内存使用量并降低 CPU 速度。 当你重复一个测试几百万次时,你基本上是在创建一个非常大的字符串并编译一个庞大的函数。

使用循环展开时的另一个警告是测试可以通过 return 语句提前退出。 编译一个无论如何都会在第 3 行返回的百万行函数毫无意义。 有必要检测早期退出并在需要时通过循环校准回退到 while 循环(模式 A)

函数体提取

在 Benchmark.js 中,使用了一种略有不同的技术。 我们可以说它使用了模式 A、B、C 和 D 的最佳部分。由于内存问题,我们没有展开循环。 为了减少可能使结果不太准确的因素,并允许测试访问本地方法和变量,我们为每个测试提取函数体。 例如,当测试这样的代码时:

var x = 1;
var y = '1';

function test() {
    x == y;
}

while (iterations--) {
    test();
}

// This would compile to:

var x = 1;
var y = '1';
while (iterations--) {
    x == y;
}

之后,Benchmark.js 使用了与 JSLitmus 类似的技术:我们在 while 循环中运行提取的代码(模式 A),重复它直到达到最短时间(模式 B),然后多次重复整个过程以产生统计结果。


需要考虑的一些事情

不准确的毫秒计时器

在某些浏览器/操作系统组合中,由于各种问题,计时器可能不准确。

例如:

Windows XP 启动时,典型的默认时钟中断周期为 10 毫秒,尽管在某些系统上使用的周期为 15 毫秒。 这意味着每 10 毫秒,操作系统就会从系统定时器硬件接收到一个中断。

一些较旧的浏览器(例如 IE、Firefox 2)依赖于内部操作系统计时器,这意味着每次调用 new Date().getTime() 时,它只会直接从操作系统中获取它。 显然,如果内部计时器仅每 10 或 15 毫秒更新一次,则测量的不确定性会增加,测试结果的准确性会大大降低。 我们需要解决这个问题。

幸运的是,可以使用 JavaScript 获得最小的度量单位。 之后,我们可以使用一点数学来将测试结果的百分比不确定性降低到 1%。 为此,我们需要将最小测量单位除以 2 以获得不确定性。 假设我们在 Windows XP 上使用 IE6,最小的度量单位是 15 毫秒。 在这种情况下,不确定性等于 15 ms / 2 = 7.5 ms。 我们希望这个数字只表示 1%,所以我们只用它除以 0.01,这给了我们所需的最短测试时间:7.5 / 0.01 = 750 ms

替代计时器

当使用 --enable-benchmarking 标志运行时,Chrome 和 Chromium 会公开一个 chrome.Interval 方法,该方法可用作高分辨率微秒计时器。

在开发 Benchmark.js 时,John-David Dalton 偶然发现了 Java 的纳秒计时器,并立即使用一个微型 Java 小程序将其暴露给 JavaScript。 看看这里是否有更多使用其他浏览器插件的可能性会很有趣。

使用更高分辨率的计时器可以缩短测试时间,从而允许更大的样本量,从而产生更小的结果误差范围。

Firebug 禁用 Firefox 的 JIT

启用 Firebug 附加组件会有效地禁用 Firefox 的所有高性能即时 (JIT) 本机代码编译,这意味着我们将在解释器中运行测试。 换句话说,我们的测试将比其他情况下运行得慢得多。 在 Firefox 中运行基准测试之前,应该始终记住禁用 Firebug。

尽管那里的影响似乎要小得多,但其他浏览器检查器工具也是如此,例如 WebKit 的 Web Inspector 或 Opera 的 Dragonfly。 在运行基准测试时避免打开这些,因为它可能会影响结果。

浏览器错误和功能

具有某种循环机制的基准容易受到各种浏览器怪癖的影响,正如最近 IE9 的死代码删除所证明的那样。 Mozilla 的 TraceMonkey 引擎中的错误,或 Opera 11 的 querySelectorAll 结果缓存也会影响基准测试结果。 在创建测试用例时记住这一点很重要。

统计学意义

大多数基准测试/基准测试脚本产生的结果在统计上并不显着。 John Resig 之前在他关于 JavaScript 基准质量的文章中写过这个。 总之,需要考虑每一个结果的误差范围,尽可能的降低误差。 更大的样本量,由完整的测试运行组成,有助于减少误差幅度。

跨浏览器测试

如果我们想在不同的浏览器中运行基准测试并获得可靠的结果,请务必在真实的浏览器中进行测试。 不要依赖 Internet Explorer 的兼容模式——这些与他们模拟的实际浏览器版本不同。

另外,请注意,IE(最高版本 8)不像所有其他浏览器那样按时间限制脚本,而是将脚本限制为 500 万条指令。 使用现代硬件,CPU 密集型脚本可以在不到半秒的时间内触发它。 如果你有一个相当快的系统,你可能会在 IE 中遇到“脚本警告”对话框,在这种情况下,最好的解决办法是修改你的 Windows 注册表以增加操作次数。 幸运的是,微软提供了一种简单的方法来做到这一点; 我们需要做的就是运行一个简单的“Fix It”向导。 更好的是,这个愚蠢的限制在 IE9 中被移除了。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便