JavaScript 基准测试
编写 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
模式 A、B 和 C 的缺点可以通过使用函数编译和循环展开来避免。
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 中被移除了。
相关文章
使用 CSS 和 JavaScript 制作文本闪烁
发布时间:2023/04/28 浏览次数:146 分类:CSS
-
本文提供了使用 CSS、JavaScript 和 jQuery 使文本闪烁的详细说明。
在 PHP 变量中存储 Div Id 并将其传递给 JavaScript
发布时间:2023/03/29 浏览次数:69 分类:PHP
-
本文教导将 div id 存储在 PHP 变量中并将其传递给 JavaScript 代码。
在 JavaScript 中从字符串中获取第一个字符
发布时间:2023/03/24 浏览次数:93 分类:JavaScript
-
在本文中,我们将看到如何使用 JavaScript 中的内置方法获取字符串的第一个字符。
在 JavaScript 中获取字符串的最后一个字符
发布时间:2023/03/24 浏览次数:141 分类:JavaScript
-
本教程展示了在 javascript 中获取字符串最后一个字符的方法