前言

  在《高性能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循环的效果和达夫设备不相上下,接下去也会针对不同的循环方式作进一步的探索。

高性能JavaScript 达夫设备的更多相关文章

  1. 高性能JavaScript 循环语句和流程控制

    前言 上一篇探讨了达夫设备对于代码性能的影响,本文主要探讨并且测试各种常见的循环语句的性能以及流程控制中常见的优化. 循环语句 众所周知,常用的循环语句有for.while.do-while以及for ...

  2. 达夫设备之js

    最近阅读<高性能JavaScript>时,在书中的“达夫设备“ . 对此,有些感悟,同时有些疑问,希望看到的朋友,能帮忙解释下,在此先提前感谢了. 1. 先说自己的理解吧: ”达夫设备“的 ...

  3. 第二篇,前端高性能JavaScript优化

    加载和执行 JavaScript是单线程,所以JavaScript的加载和执行是从上下文加载执行完一个继续加载执行下一个文件会阻塞页面资源的加载,所以一般情况下JavaScript文件放在body标签 ...

  4. 【前端性能优化】高性能JavaScript整理总结

    高性能JavaScript整理总结 关于前端性能优化:首先想到的是雅虎军规34条然后最近看了<高性能JavaScript>大概的把书中提到大部分知识梳理了下并加上部分个人理解这本书有参考雅 ...

  5. 高性能JavaScript笔记二(算法和流程控制、快速响应用户界面、Ajax)

    循环 在javaScript中的四种循环中(for.for-in.while.do-while),只有for-in循环比其它几种明显要慢,另外三种速度区别不大 有一点需要注意的是,javascript ...

  6. 《高性能Javascript》读书笔记-4

    第四章 算法和流程控制 代码组织结构和解决具体问题的思路是影响代码性能的主要因素 循环处理是最常见的编程模式之一,也是提高性能的关注点之一 循环分四种:标准for循环 ; i < Things. ...

  7. 高性能javascript笔记

    ----------------------------------------------------------- 第一章 加载和执行 ------------------------------ ...

  8. 高性能JavaScript(算法和流程控制)

    在大多与编程语言中,代码的执行时间大部分消耗在循环中,是提升性能必须关注的要点之一 循环的类型 for循环(它由四部分组成:初始化.前测条件.后执行体.循环体.) for(var i = 0; i & ...

  9. 读书笔记(高性能javascript)(一)

    1.加载与执行: (1)将脚本放在底部:(否则会阻塞) (2)由于每个<script>标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情 ...

随机推荐

  1. python里面出现中文的时候报错 'ascii' codec can't encode characters in position

    编码问题,在头部添加 import sys reload(sys) sys.setdefaultencoding( "utf-8" ) http://www.xuebuyuan.c ...

  2. 每日Scrum(2)

    今天是冲刺的第二天,小组主要做了界面的美化,加入了软件的开始动画,以及学校景点的美图介绍: 主要的问题在于除了开始界面,进入软件之后还是有待改进,功能的呈现有待加强.

  3. BIEE使用技巧

    索引: 1.如何清除缓存 2.通过“编辑 SQL”取得前一天的日期 3.格式化日历框参数 4.根据传入的开始时间和结束时间取得事实表中的指标(用到了3中的技巧) 5.直接调用数据库函数 6.时间格式转 ...

  4. 看看Parallel中高度封装的三个方法,Invoke,For和ForEach

    说到.net中的并行编程,也许你的第一反应就是Task,确实Task是一个非常灵活的用于并行编程的一个专用类,不可否认越灵活的东西用起来就越 复杂,高度封装的东西用起来很简单,但是缺失了灵活性,这篇我 ...

  5. mac 安装 php nginx mysql

    mac下面安装php nginx mysql根linux下面差不多,建议大家使用brew管理工具包安装. 1,安装homebrew http://brew.sh/index_zh-cn.html 安装 ...

  6. db2数组、函数

    一. 数组 在db2中,创建一个数组会在functions下生成两个对象:sys类型和用户类型的Array /*创建数组*/ ) array[]; /*删除数组*/ drop type arrName ...

  7. eclipse常用配置

    一. 手动方式安装svn插件 1. 根据本地的svn客户端的版本,到http://subclipse.tigris.org/ 下载eclipse对应版本的svn插件包 注:装eclipse里面的svn ...

  8. Android ImageView(scaleType属性)(转)

    (转自:http://www.open-open.com/lib/view/open1378257991687.html) <ImageView android:id="@+id/im ...

  9. AC日记——统计单词数 openjudge 1.12 5

    05:统计单词数 总时间限制:  1000ms 内存限制:  65536kB 描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次 ...

  10. 初识hibernate框架之一:进行简单的增删改查操作

    Hibernate的优势 l 优秀的Java 持久化层解决方案  (DAO) l 主流的对象—关系映射工具产品 l 简化了JDBC 繁琐的编码 l 将数据库的连接信息都存放在配置文件 l 自己的ORM ...