微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

高性能JavaScript 达夫设备

2015-07-25 20:05 前端大全

(点击上方,可快速关注)


作者:韩子迟

网址:http://www.cnblogs.com/zichi/p/4661253.html

点击“阅读原文”可查看本文网页版


前言


在《高性能JavaScript》一书的第四章算法和流程控制中,提到了减少迭代次数加速程序的策略—达夫设备(Duff’s device)。达夫设备本身很好理解,但是其效果是否真的像书中所说“如果迭代次数超过1000,那么达夫设备的执行效率将明显提升”?还是随着浏览器性能的逐渐增强,这种以牺牲代码阅读性而获取的性能提升已经微不足道?


达夫设备


达夫设备真的很简单,说白了就是“循环体展开”。看如下的代码:


var a = [0, 1, 2, 3, 4];

var sum = 0;

for(var i = 0; i < 5; i++)

sum += a[i];

console.log(sum);


我们将循环体展开来写:


var a = [0, 1, 2, 3, 4];

var sum = 0;

sum += a[0];

sum += a[1];

sum += a[2];

sum += a[3];

sum += a[4];

console.log(sum);


因为少作了多次的for循环,很显然这段代码比前者效率略高,而且随着数组长度的增加,少作的for循环将在时间上体现更多的优势。


达夫设备这种思想或者说是策略,原来是运用在C语言上的,Jeff Greenberg将它从C语言移植到了JavaScript上,我们可以来看看他写的模板代码:


var iterations = Math.floor(items.length / 8),

startAt = items.length % 8,

i = 0;

do {

switch(startAt) {

case 0: process(items[i++]);

case 7: process(items[i++]);

case 6: process(items[i++]);

case 5: process(items[i++]);

case 4: process(items[i++]);

case 3: process(items[i++]);

case 2: process(items[i++]);

case 1: process(items[i++]);

}

startAt = 0;

} while(--iterations);


注意看switch/case语句,因为没有写break,所以除了第一次外,之后的每次迭代实际上会运行8次!Duff’s Device背后的基本理念是:每次循环中最多可调用8次process()。循环的迭代次数为总数除以8。由于不是所有数字都能被8整除,变量startAt用来存放余数,便是第一次循环中应调用多少次process()。


此算法一个稍快的版本取消了switch语句,将余数处理和主循环分开:


var i = items.length % 8;

while(i) {

process(items[i--]);

}

i = Math.floor(items.length / 8);

while(i) {

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

}


尽管这种方式用两次循环代替了之前的一次循环,但它移除了循环体中的switch语句,速度比原始循环更快。


性能测试


接着我们来进行达夫设备的性能测试。如果迭代中的操作复杂的话,会减小达夫设备优化对于时间的影响,所以循环内部我只选取了简单的操作;而且为了方便操作,选取了8的倍数作为数组长度。


var a = [];

var times = 1000;

// init array

for(var i = 1; i <= times; i++)

a[i] = i;

// ordinary way

console.time('1');

var sum = 0;

for(var i = 1; i <= times; i++)

sum += 1 / a[i];

console.log(sum);

console.timeEnd('1');

// Duff's device

console.time('2');

var sum = 0;

while(times) {

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

sum += 1 / a[times--];

}

console.log(sum);

console.timeEnd('2');


当times取值较小时,得到了这样的结果(chrome 版本 43.0.2357.134 m):



此时心中一万头草泥马奔腾而过,默默问自己为什么会出现这样有悖常理的结果。直到times取值越来越大:



而在firefox(39.0)下则出现了更诡异的一幕,似乎达夫设备对其不起任何效果:



那么达夫设备真的达不到想象当中的优化程度了吗?为了验证自己的猜想,同时在网上找到了一个外国朋友写的测试代码JavaScript Loop Optimization,大多数的测试结果还是和预料一致,但是也能捕获到这样的截图:



总结


经过测试,我觉得在迭代次数少的情况下,完全没有必要用达夫设备进行优化,且不说代码可读性差,有时甚至会适得其反,而多大的迭代次数算多,多大算少,也不好说,特定的浏览器特定的版本都有其一定的取值。老版本的浏览器运用达夫设备优化性能能得到大幅度的提升,而新版的浏览器引擎肯定对循环迭代语句进行了更强的优化,所以达夫设备能实现的优化效果日趋减弱甚至于没有。而在查阅资料的过程中,有人提出while循环的效果和达夫设备不相上下,接下去也会针对不同的循环方式作进一步的探索。




前端大全

微信号:FrontDev

打造东半球最好的 前端技术 微信号

--------------------------------------

商务合作QQ:2302462408

招聘和猎头服务QQ:2302462408

投稿网址:top.jobbole.com

 
前端大全 更多文章 5个典型的JavaScript面试题(上) Limu:JavaScript的那些书 Web开发:我希望得到的编程学习路线图 JavaScript基础工具清单 常用排序算法之JavaScript实现
猜您喜欢 喜!又有一名博赛同学通过HCIE认证考试 【上海】布丁动画招聘 Android、后端工程师,推荐成功就送 iPhone 6! 115个Java面试题和答案——终极列表(上) 运行维护管理制度|拿走不谢~ React系列之(二)-React组件的生命周期