发现网络上一些javascript/as 实现Duff’s Device 循环算法的错误。

今天无意在一个朋友的Blog上看见有Duff’s Device循环算法(原理BaiduGoogle有详细介绍)的文章,点进去看了看,发现问题多多。

代码

//credit: Jeff Greenberg
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++]);
}
}while(--iterations);

 和优化后的版本

//credit: Jeff Greenberg
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--]);
}

  看了一下,错误颇多,也可能也是网上转帖,没有仔细研究过此算法的原理。

一版代码

1,将swtich引入到了do中增加了每次循环的性能开销,原版算法中使用switch+case+do其实是利用了C语言的编译机制的"跌落"行为,实现了switch只会在首次运行起效的功能.

2,startAt未置0 -> 此处变量只是用于计算出首次运行批次执行操作次数,只能第一批次有效,运行完之后应该置为0,保证此后每批次运行8次。

3,iterations 计算错误 -> Math.floor是返回小于等于数字参数的最大整数,对数字进行下舍入,在while循环中边界检测时会导致最终循环批次会少一次。

4,边界值检测不严谨 , 当iterations = 0 时,会陷入死循环

5,未考虑空序列

二版代码

1,利用了while循环进行首批次运行,其实是增加了一轮n > 0 的边界值检测,虽然至多增加8次的比对,性能也差不多,但失去了原版算法的原意。

2,i的两次赋值. 第一次赋值可以认为是算出执行不满8位的批次的操作数,第二次赋值可以认为是剩余项的执行批次.但是执行取item数组索引时,使用i--,这个完全是错误操作,把批次和当前项索引搞错了。直接导致报错

贴一段我整合修改后的版本

 1 function doSth(num) {
2 console.log(num);
3 }
4
5
6 //new version
7 function ForEachExtend(items, process) {
8 if (items.length > 0) {
9 var startAt = items.length % 8, i = 0, iterations = Math.ceil(items.length / 8);
10
11 switch (startAt) {
12 case 0: process(items[i++]);
13 case 7: process(items[i++]);
14 case 6: process(items[i++]);
15 case 5: process(items[i++]);
16 case 4: process(items[i++]);
17 case 3: process(items[i++]);
18 case 2: process(items[i++]);
19 case 1: process(items[i++]);
20 }
21
22 while (--iterations > 0) {
23 process(items[i++]);
24 process(items[i++]);
25 process(items[i++]);
26 process(items[i++]);
27 process(items[i++]);
28 process(items[i++]);
29 process(items[i++]);
30 process(items[i++]);
31 }
32 }
33 }
34
35 console.log("空数组===>");
36 ForEachExtend([], doSth);
37 console.log("长度为1数组===>");
38 ForEachExtend([1], doSth);
39 console.log("长度可被整除数组===>");
40 ForEachExtend([1, 2, 3, 4, 5, 6, 7, 8], doSth);
41 console.log("长度>8数组===>");
42 ForEachExtend([1, 2, 3, 4, 5, 6, 7, 8, 9], doSth);

上网查询了一下网络相关的javascript实现,发现基本都是千篇一律,不管是AS还是Javascript实现都或多或少存在我上面发现的问题,代码基本都是雷同,这些问题都是通过一定的代码阅读都能发现的,结果基本都是顺手粘贴,也不考究测试一下是否可行,真是天下文章一大抄,送到嘴边的肉都不嚼一下就咽下去了。

另在网络上的JavaScript高性能编程这本书上也发现是这么写,让我很是意外。这个问题难道是我搞错了?很是奇怪.