迹忆客 专注技术分享

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

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 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Do you understand JavaScript closures?

发布时间:2025/02/21 浏览次数:108 分类:JavaScript

The function of a closure can be inferred from its name, suggesting that it is related to the concept of scope. A closure itself is a core concept in JavaScript, and being a core concept, it is naturally also a difficult one.

Do you know about the hidden traps in variables in JavaScript?

发布时间:2025/02/21 浏览次数:178 分类:JavaScript

Whether you're just starting to learn JavaScript or have been using it for a long time, I believe you'll encounter some traps related to JavaScript variable scope. The goal is to identify these traps before you fall into them, in order to av

How much do you know about the Prototype Chain?

发布时间:2025/02/21 浏览次数:150 分类:JavaScript

The prototype chain can be considered one of the core features of JavaScript, and certainly one of its more challenging aspects. If you've learned other object-oriented programming languages, you may find it somewhat confusing when you start

JavaScript POST

发布时间:2024/03/23 浏览次数:96 分类:JavaScript

本教程讲解如何在不使用 JavaScript 表单的情况下发送 POST 数据。

扫一扫阅读全部技术教程

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

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便