[转] 使用Duff's Device算法优化for循环
2011-02-14 10:25
148 查看
注意:
经过Aone的提醒发现一个问题,如果为了代码的可读性而将process()封装为函数,反而会导致增加了一次函数调用的指针跳转,拖慢了程序得不偿失。因此只推荐在需要极限优化超过代码可读性的情况下使用。
Duff's Device算法是一个老东西了,最早是在1983年C上由Tom Duff实现,然后2001年Jeff Greenberg移植到JavaScript上。算是很久的一个优化方案了 -_-b...竟然到现在才被发现。
话不多少,绕回正题,在遍历数组时众所周知的方法就是使用标准的for循环
var array:Array;//假设已有数据
标准方法:var length:int = array.length;
for(var i:int = 0; i < length; i++) {
process(array[i] as xxx);
}
复制代码一个基本的结构下来,除开process函数的代码质量不说,最大的开销放在 i < length 的Boolen比较之上,Duff's Device算法的目的即在于减少迭代的次数从而获得代码效率的提升。
Duff's Device实现一:var iterations:int = Math.floor(array.length / 8);
var startAt:int = array.length % 8;
var i:int = 0;
do {
swtich (startAt) {
case 0: process(array[i++]);
case 7: process(array[i++]);
case 6: process(array[i++]);
case 5: process(array[i++]);
case 4: process(array[i++]);
case 3: process(array[i++]);
case 2: process(array[i++]);
case 1: process(array[i++]);
}
startAt = 0;
} while(--iterations);
复制代码Duff's Device的基本理念是:在每次while循环中调用8次process函数,当然由于不是每个array都能被8整除,所以通过变量startAt记录下多出的余数,并通过switch在第一次循环时process多出来的部分。
通过这种巧妙的写法,迭代的次数被减少到了1/8左右。有人问那为什么是8,这里没有认真考究,但据说是跟内存的分配为8的倍数有关,每一次迭代进行8次process,有较大的可能分配到内存条相邻的的区间,从而提高跟寄存器的读取命中策略(Tom Duff当初貌似就是从汇编里取得灵感才写出最早那段C的代码),从而达到提高效率的目的。
Duff's Device优化实现(忘了经典实现吧,这个更好):var i:int = items.length % 8;
while(i) {
process(array[i--]);
}
i = Math.floor(array.length);
while(i) {
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
}
复制代码这里将非8的余数的部分单独抽离出来,减少了switch的判断,从而比经典循环更快。
效果:
在AS上做了测试,在100万次for,process函数为单步运算时,普通的for循环耗时40ms左右,优化版本的Duff's Device运行时间仅为4ms。可以预见在粒子动画和做游戏时遍历对象时和碰撞检测时应该会有很棒的效率提升。
当然不得不吐槽的是这种底层的优化不应该由这种“奇技淫巧”来实现,毕竟这样影响了代码的可读性。这部分的优化更应该在将for循环转化为机器码时做好处理。
经过Aone的提醒发现一个问题,如果为了代码的可读性而将process()封装为函数,反而会导致增加了一次函数调用的指针跳转,拖慢了程序得不偿失。因此只推荐在需要极限优化超过代码可读性的情况下使用。
Duff's Device算法是一个老东西了,最早是在1983年C上由Tom Duff实现,然后2001年Jeff Greenberg移植到JavaScript上。算是很久的一个优化方案了 -_-b...竟然到现在才被发现。
话不多少,绕回正题,在遍历数组时众所周知的方法就是使用标准的for循环
var array:Array;//假设已有数据
标准方法:var length:int = array.length;
for(var i:int = 0; i < length; i++) {
process(array[i] as xxx);
}
复制代码一个基本的结构下来,除开process函数的代码质量不说,最大的开销放在 i < length 的Boolen比较之上,Duff's Device算法的目的即在于减少迭代的次数从而获得代码效率的提升。
Duff's Device实现一:var iterations:int = Math.floor(array.length / 8);
var startAt:int = array.length % 8;
var i:int = 0;
do {
swtich (startAt) {
case 0: process(array[i++]);
case 7: process(array[i++]);
case 6: process(array[i++]);
case 5: process(array[i++]);
case 4: process(array[i++]);
case 3: process(array[i++]);
case 2: process(array[i++]);
case 1: process(array[i++]);
}
startAt = 0;
} while(--iterations);
复制代码Duff's Device的基本理念是:在每次while循环中调用8次process函数,当然由于不是每个array都能被8整除,所以通过变量startAt记录下多出的余数,并通过switch在第一次循环时process多出来的部分。
通过这种巧妙的写法,迭代的次数被减少到了1/8左右。有人问那为什么是8,这里没有认真考究,但据说是跟内存的分配为8的倍数有关,每一次迭代进行8次process,有较大的可能分配到内存条相邻的的区间,从而提高跟寄存器的读取命中策略(Tom Duff当初貌似就是从汇编里取得灵感才写出最早那段C的代码),从而达到提高效率的目的。
Duff's Device优化实现(忘了经典实现吧,这个更好):var i:int = items.length % 8;
while(i) {
process(array[i--]);
}
i = Math.floor(array.length);
while(i) {
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
process(array[i--]);
}
复制代码这里将非8的余数的部分单独抽离出来,减少了switch的判断,从而比经典循环更快。
效果:
在AS上做了测试,在100万次for,process函数为单步运算时,普通的for循环耗时40ms左右,优化版本的Duff's Device运行时间仅为4ms。可以预见在粒子动画和做游戏时遍历对象时和碰撞检测时应该会有很棒的效率提升。
当然不得不吐槽的是这种底层的优化不应该由这种“奇技淫巧”来实现,毕竟这样影响了代码的可读性。这部分的优化更应该在将for循环转化为机器码时做好处理。
相关文章推荐
- 使用多线程优化双重for循环校验
- 使用Map对于两层for循环的一个优化
- 两张Excel表比较,两个for循环比较优化使用Contains
- SQLCE使用本地数据库优化
- 使用NOSQL的MongoDB时建立索引需要注意的几点建议和Explain优化分析 (转)
- Android 性能优化 三 布局优化ViewStub标签的使用
- Nginx使用的php-fpm的两种进程管理方式及优化(转)
- Kylin 的优化以及使用总结
- Windows 7优化调整使用小技巧
- ASP.NET MVC 3 网站优化总结(四)使用缓存
- JAVA程序中使用ORACLE绑定变量来优化数据库
- win8.1使用及优化
- MySQL查询优化技术系列讲座之使用索引(一)
- Mongodb使用explain优化查询(1)--explain输出参数解析
- 语法使用优化(c++)
- django中for循环的使用
- mysql explain的使用(优化查询)
- -------------------Android代码优化——使用Android lint工具
- 关于一个ListView使用多个item布局在优化时出现显示错乱的解决方案
- Android使用缓存优化ListView