美文网首页
深入JavaScript数组:进化与性能

深入JavaScript数组:进化与性能

作者: 2f1b6dfcc208 | 来源:发表于2017-10-30 17:15 被阅读96次

    /* 原文阅读自前端早读课1071期 */

    为什么说JavaScript数组不是真正的数组

      数组是用来存储元素的线性集合,在内存中占据一串连续的内存位置。注意重点,“连续”(continuous)。

    如图
    上图展示了数组在内存中的存储方式,这个数组保存了4个元素,每个元素4个字节,加起来总共占用了16字节的内存区。
      假设我们声明了 一个元素全为整数的数组arr[4],分配到的内存区的地址从1201开始。一旦需要读取arr[2],只需要通过数学计算拿到arr[2]的地址即可,计算1201+(2*4),直接从1209开始读取。
      然而在JavaScript中,数组并不是你想象中的那样连续的(continuous),因为它本质上属于一种特殊的对象,其实现类似哈希映射(hash-maps)或字典(dictionaries),如链表。所以,如果在JS中声明一个数组const arr = new Array(4),计算机将生成类似下图的结构,如果程序需要读取arr[2],仍需要从1201开始遍历寻址。 如图

    这就是JS 数组与真实数组的不同之处,显而易见,数学计算比遍历链表快,就长数组而言,情况尤其如此。

    JS数组的进化

      近几年来,JS的标准不断完善,性能也在不断提升。实际上,现代的JS引擎是会给数组分配连续内存的--如果数组是同质的(所有元素类型相同)。优秀的程序员总会保证数组同质,以便JIT(即时编译器)能够使用c编译器式的计算方式读取元素。

    不过,一旦你想要在某个同质数组中插入一个其它类型的元素,JIT将解构整个数组,并按照旧有的方式重新创建。

    因此,如果你的代码写的不太糟,JS Array对象在幕后仍然保持着真正的数组形式,这对现代JS开发者来说极为重要。

    此外,数组跟随ES2015有了更多的演进,TC39决定引入类型化数组(Typed Arrays),于是我们就有了ArrayBuffer。

    ArrayBuffer提供一块连续内存供我们随意操作。然而,直接操作内存还是太复杂、偏底层,于是便有了处理ArrayBuffer的视图(View)。目前已有一些可用视图,未来还会有更多加入。

    var buffer = new ArrayBuffer(8);
    var view = new Int32Array(buffer);
    view[0] = 100;
    

    高性能、高效率的类型化数组在WebGl之后被引入。WebGL工作者遇到了极大的性能问题,即如何高效处理二进制数据。另外,你也可以使用SharedArrayBuffer在多个Web Worker进程之间共享数据,以提升性能。

    旧式数组 VS 类型化数组 :性能

    前面已经讨论了JS数组的演进,现在来测试现代数组到底能给我们带来多大收益(环境:windows操作系统 node v8.1.3)

    • 旧式数组:插入
    const LIMIT = 10000000;
    const arr = new Array(LIMIT);
    console.time('Array insertion time');
    for (let i = 0; i < LIMIT; i++) {
      arr[i] = i;
    }
    console.timeEnd('Array insertion time');//26ms
    
    • Typed Array:插入
    const LIMIT = 10000000;
    const buffer = new ArrayBuffer(LIMIT * 4);
    const arr = new Int32Array(buffer);
    console.time('Array insertion time');
    for (let i = 0; i < LIMIT; i++) {
        arr[i] = i;
    }
    console.timeEnd('Array insertion time');//30ms
    

    旧式数组和ArrayBuffer的性能不相上下?NoNoNo,出现这种情况的原因是因为现代编译器已经智能化,能够将元素类型相同的传统数组在内部转换为内存连续的数组。尽管使用了new Array(LIMIT),数组实际依然以现代数组形式存在。

    接着修改第一例子,将数组改成异构型(元素类型不完全一致)的,来看看是否存在性能差异。

    • 旧式数组:插入
    const LIMIT = 10000000;
    const arr = new Array(LIMIT);
    arr.push({a:1})
    console.time('Array insertion time');
    for (let i = 0; i < LIMIT; i++) {
      arr[i] = i;
    }
    console.timeEnd('Array insertion time');//756ms
    

    改变发生在第三行,将数组变为异构类型,其余代码保持不变,性能差异表现出来了,慢了29倍。

    • 旧式数组:读取
    const LIMIT = 10000000;
    const arr = new Array(LIMIT);
    arr.push({a:1})
    for (let i = 0; i < LIMIT; i++) {
      arr[i] = i;
    }
    
    let p;
    
    console.time('Array read time');
    for(let i=0;i<LIMIT;i++){
      p=arr[i];
    }
    console.timeEnd('Array read time');//116ms
    
    • Typed Array:读取
    const LIMIT = 10000000;
    const buffer = new ArrayBuffer(LIMIT * 4);
    const arr = new Int32Array(buffer);
    for (let i = 0; i < LIMIT; i++) {
        arr[i] = i;
    }
    
    let p;
    
    console.time('Array read time');
    for(let i=0;i<LIMIT;i++){
      p=arr[i];
    }
    console.timeEnd('Array read time');//119ms
    

    此处的测试应该是不够准确,我发现在上述的所有例子中,当把let替换为var时,耗时显著减少,这里应该是创建块级作用域耗费了性能,似乎无法证明Typed Array的性能。

      虽然测试没有获得可信的数据,但类型化数组的引入是有显著意义的,Int8Array,Uint8Array,Uint8ClampedArray,
    Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,这些是类型化数组视图,使用原生字节序(与本机相同),还可以使用Data View 创建自定义视图窗口。未来应该会有更多帮助我们轻松操作ArrayBuffer的Data View库。JS数组的演进非常棒,现在它们速度快、效率高、健壮,在内存分配时也足够智能。

    相关文章

      网友评论

          本文标题:深入JavaScript数组:进化与性能

          本文链接:https://www.haomeiwen.com/subject/oyappxtx.html