JavaScript - 将数组部分快速复制到另一个数组的最快方法

3

我需要快速将一个数组的一部分复制到另一个数组中,替换它的旧值。

  • 不需要范围检查。
  • 要复制的项目数:16384
  • 该数组仅包含整数。

基准测试代码: http://codebase.es/test/copytest.htm

这是我的方法:

  var i = 0x4000>>5; // loops count
  var j = 0x4000;    // write start index
  var k = 0x8000;    // read start index
  while (i--) {      // loop unrolling
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];    
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];
    //8
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];    
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];
    //16
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];    
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];
    //24
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];    
    dst[j++]=src[k++]; dst[j++]=src[k++];
    dst[j++]=src[k++]; dst[j++]=src[k++];
    //32
  }    

能否更快地完成这个任务?


3
使用前置递减/递增而不是后置递减/递增可能会带来轻微的性能提升。 - Gumbo
Gumbo,你说得对。它会快一点。如果你把你的评论写成答案,而且没有更好的解决方案,我会给你的。 - Peter
我知道这已经很晚了,但我很好奇。为什么不只是使用 var dst = src.concat() ?我认为这可能是克隆数组最快的方法。但这并不是深拷贝。对于深拷贝来说,循环是唯一的方法。 - Shripad Krishna
7个回答

4
我不确定你的方法是否比这个更快:
var i = 0x4000;     // loops count
var j = 0x4000;    // write start index
var k = 0x8000;    // read start index
while (i--) {      // loop unrolling
  dst[j++]=src[k++];
}

它已经在Chrome、FF3.5和Opera中进行了测试。请查看: http://codebase.es/test/copytest.htm - Peter

2

你可以继续展开循环,以获得更微小的性能提升,但是这似乎已经是你能够获得的最快速度了。正如Gumbo在评论中所述,尝试使用前缀递增而不是后缀递增:

var i = 0x4000>>5 + 1; // loops count
var j = 0x4000 - 1;    // write start index
var k = 0x8000 - 1;    // read start index
while (--i) {      // loop unrolling
    dst[++j]=src[++k]; dst[++j]=src[++k];
    dst[++j]=src[++k]; dst[++j]=src[++k];    
    dst[++j]=src[++k]; dst[++j]=src[++k];
    dst[++j]=src[++k]; dst[++j]=src[++k];
    //8
    ...

1

抱歉,一年后才回复.. 但这是在FF中最快的:

function copy4() {
  var i = 0x4000; // loops count
  var j = 0x4000; // write start index
  var k = 0x8000; // read start index

  var args = src.slice(k, k+i);
  args.unshift(i);
  args.unshift(j);
  Array.prototype.splice.apply(dst,args);
}

0
我会考虑使用切片方法:
var dst = src.slice(start,end)

性能取决于JavaScript引擎的实现,但是可以假设所有浏览器都在其平台上尽可能地实现了高效。

在此处查看更多


1
他要求替换另一个数组的内容,而不是创建一个新数组。 - Cem Kalyoncu
@cemkalyoncu:然后使用 slicesplice - Gumbo
4
“Slice and splice”听起来像是电视直销产品。“厌倦了笨拙的for循环吗?试试新的Slice and Splice!它可以切片,它可以拼接,它可以消除数组处理中的痛苦。现在只需19.95美元即可下单!” - Matthew Crumley
slice + splice 的速度要慢得多。请看 copy 3 的代码: http://codebase.es/test/copytest.htm - Peter

0

一个值得进行基准测试的替代方案可能是构建一个全新的dst数组,其中只使用一些强大的原始方法,而不是执行循环,即:

dst = dst.slice(0, writestart).concat(
    src.slice(readstart, readstart+count),
    dst.slice(writestart+count));

这种方法的性能与循环相比如何,无疑会因涉及的数组长度和计数以及底层Javascript引擎而有所不同--猜测并不是很有成效,这就是为什么我建议进行基准测试的原因;-)。


这会慢大约10倍。 - Peter
一些内部函数,最有可能是用C++编写的,在执行时比解释一堆代码要慢,很奇怪。顺便说一下,我在C语言中测试了相同的代码,结果复制时间为6-14毫秒(1000 x 0x4000个元素)。 - Cem Kalyoncu
1
现代浏览器会将JavaScript编译成机器码。例如,Chrome的V8和Firefox的TraceMonkey。 - Peter

0

尝试使用内置方法{{link1:slice}}和{{link2:splice}}的组合:

Array.prototype.splice.apply(dst, [j, i].concat(src.slice(k, k+i)));

抱歉,您的解决方案大约慢了50倍。 - Peter
不要这样做,如果你想让它表现良好。 - Derrick

0

你可以通过完全展开的方式来创建一个版本,例如:

function compileCopy(count, di, si) {
    var sb = new Array(count);
    di += count, si += count;
    while(count--) sb[count] = --di + ']=src[' + --si;
    return new Function('dst[' + sb.join('];dst[') + '];');
}

var copy = compileCopy(0x4000, 0x4000, 0x8000);

在Opera中,它将比循环版本稍微快一些。在FF中...不是(可能还有一些bug)。

但是起始索引已经硬编码到“编译”函数中。每次复制调用都需要再次“编译”。 - Peter
但它们可以作为函数参数而不会影响性能。 - Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接