由lodash引出数组Array的slice方法性能探究

由lodash引出数组Array的slice方法性能探究

lodash 源码中,发现数组截取操作并没有直接使用arrayslice方法,而是写了个 baseSlice 方法,更重要的是 baseSlice 方法里,也并没有使用 slice 方法,而是使用循环索引的方式去实现。这让我陷入思考,难道原生Array的slice方法还不如循环快?

lodash 源码如下:

function baseSlice(array, start, end) {
  var index = -1,
      length = array.length;

  if (start < 0) {
    start = -start > length ? 0 : (length + start);
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : ((end - start) >>> 0);
  start >>>= 0;

  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

于是就开始写代码比较:

操作长度为100的数组,执行10000次:

var baseArray = [];
var i = -1;
while (++ i < 100) { // 定义一个10000长度的数组以供使用
  baseArray[i] = i;
}
console.time('slice with no args');
for (var i = 0; i < 10000; i++){
  baseArray.slice();
}
console.timeEnd('slice with no args');
console.time('slice with args');
for (var i = 0; i < 10000; i++){
  baseArray.slice(20, 40);
}
console.timeEnd('slice with args');
console.time('index');
for (var i = 0; i < 10000; i++){
  var result = Array(100);
  var index = -1;
  var length = baseArray.length;
  while (++index < length) {
    result[index] = baseArray[index];
  }
}
console.timeEnd('index');
console.time('index with no init array');
for (var i = 0; i < 10000; i++){
  var result = [];
  var index = -1;
  var length = baseArray.length;
  while (++index < 20) {
    result[index] = baseArray[index];
  }
}
console.timeEnd('index with no init array');

对比结果:

// chrome 版本 54.0.2840.98 (64-bit)
slice with no args: 93.3ms
slice with args: 175ms
index: 365ms
index with no init array: 546ms
// safari 版本 9.0.2 (11601.3.9)
slice with no args: 122.152ms
slice with args: 117.181ms
index: 60076.301ms
index with no init array: 62055.406ms
//firefox 50.0
slice with no args: 462.07ms
slice with args: 442.25ms
index: 194206.08ms
index with no init array: 184714.59ms

由于mac ox无法测试IE, 可以看出,chrome总体要比safafi、firefox快很多; slice方法上,safari和chrome差不多, chrome下slice有参数比无参数慢; 新建数组Array()和 []性能差不多;

这么看来,那用原生 slice 明显会更好,为什么 lodash 要用循环索引呢?难道是因为一般应用数组都不会这么大,那改成长度为100的数组进行操作;

// chrome
slice with no args: 6ms
slice with args: 4.12ms
index: 5.82ms
index with no init array: 7.96ms
// safari
slice with no args: 9.158ms
slice with args: 8.432ms
index: 640.311ms
index with no init array: 643.174ms
//firefox
slice with no args: 17.56ms
slice with args: 17.53ms
index: 2174.15ms
index with no init array: 2198.15ms

发现结论并没有改变。 然后就向lodash提了question, 回复是:

The perf wins of Array#slice vs. baseSlice depends on the size of the array. That is a minor point though as the perf of that method is not likely to be an issue. The reason we use baseSlice is because we treat arrays as dense while Array#slice will respect sparse arrays.

大概意思是,他觉得性能没有太大的差别,Array#slice和baseSlice性能强弱取决于数组的大小。这里采用baseSlice是因为他们要视所有为密集数据,而Array#slice会处理稀疏数组。

然后就有下面的测试:

使用 Array#slice和baseSlice 分别截取数组其中一段,循环1000000次:

var baseArray = [];
var i = -1;
while (++ i < 100000) { // 定义一个10000长度的数组以供使用,这里数组长度并不影响性能,性能影响主要在于截取的长度。
  baseArray[i] = i;
}
console.time('slice with args');
for (var i = 0; i < 1000000; i++){
  baseArray.slice(0, 20);
}
console.timeEnd('slice with args');
console.time('index');
for (var i = 0; i < 1000000; i++){
  var result = Array(20);
  var index = -1;
  while (++index < 20) {
    result[index] = baseArray[index];
  }
}
console.timeEnd('index');

不同截取长度在chrome下的效果:

// 截取长度 20
slice with args: 110ms
index: 78.9ms
// 截取长度 30
slice with args: 114ms
index: 112ms
// 截取长度 30
slice with args: 132ms
index: 146ms

由此看出,截取长度为30左右时,Array#slice和loop方式性能基本一样,而 Array#slice 基本不受截取长度的影响。 所以结论是,当长度为30以下时,lodash的方法是比较高效的,当然,这影响微乎其微。像_.chunk方法,一般场景分组都不会太长。

疑惑是,对我的回复的后半句我没怎么看懂,密集数据和稀疏数据对这有什么影响呢?

0%