JS:从函数返回2个数字的最快方法?

时间:2016-07-30 22:43:24

标签: javascript performance

有没有办法超越以下方法? (a和b的类型是已知的 - unsigned int 32 bit):

function f() {
    var a = /*calculate a*/;
    var b = /*calculate b*/;
    return [a, b];
}

这会更快吗?是的,我知道,以下代码很糟糕。但我们的想法是避免创建新对象(如第一个示例中的返回数组)。

// set of general-purpose registers for UINT32 values.
var global_registers = new Uint32Array(256);

function f() {
    global_registers[0] = a;
    global_registers[1] = b;
}

// Usage:
f();

//use the global_registers[0] and global_registers[1].

3 个答案:

答案 0 :(得分:4)

从您提供的两个版本的代码中,第二个代码更快。但这并不意味着使用类型化数组正在发挥作用。您还切换到全局变量,改变它们而不是创建新条目,并且不要让您的函数返回任何内容。

以下是这些变体的性能测试:

  1. 您的原始阵列解决方案
  2. 使用普通对象而不是数组

    function f_obj() {
        var a = 1;
        var b = 2;
        return {a, b};
    }
    
  3. 使用作为参数传递的全局普通对象

    function f_obj_inplace(obj) {
        var a = 1;
        var b = 2;
        obj.a = a
        obj.b = b;
    }
    
  4. 您原来的全球"注册"溶液

  5. 使用标准全局对象

    var global_obj = { a: 0, b: 0};
    
    function f_glob_obj() {
        var a = 1;
        var b = 2;
        global_obj.a = a;
        global_obj.b = b;
    }
    
  6. 
    
    //////////////////////////////////////
    function f_arr() {
        var a = 1;
        var b = 2;
        return [a, b];
    }
    
    //////////////////////////////////////
    function f_obj() {
        var a = 1;
        var b = 2;
        return {a, b};
    }
    
    //////////////////////////////////////
    var global_registers = new Uint32Array(256);
    
    function f_glob_reg() {
        var a = 1;
        var b = 2;
        global_registers[0] = a;
        global_registers[1] = b;
    }
    
    //////////////////////////////////////
    var global_obj = { a: 0, b: 0};
    
    function f_glob_obj() {
        var a = 1;
        var b = 2;
        global_obj.a = a;
        global_obj.b = b;
    }
    
    var iterations = 1000000;
    var o, result;
    /////////////////////
    var start = performance.now();
    for(var i = 0; i < iterations; i++) {
        o = f_arr();
        result = o[0];
    }
    console.log('f_arr', (performance.now() - start).toFixed(2));
    /////////////////////
    var start = performance.now();
    for(var i = 0; i < iterations; i++) {
        o = f_obj();
        result = o.a;
    }
    console.log('f_obj', (performance.now() - start).toFixed(2));
    /////////////////////
    var obj = { a: 0, b: 0 };
    var start = performance.now();
    for(var i = 0; i < iterations; i++) {
        f_obj(obj);
        result = obj.a;
    }
    console.log('f_obj_inplace', (performance.now() - start).toFixed(2));
    /////////////////////
    var start = performance.now();
    for(var i = 0; i < iterations; i++) {
        f_glob_reg();
        result = global_registers[0];
    }
    console.log('f_glob_reg', (performance.now() - start).toFixed(2));
    /////////////////////
    var start = performance.now();
    for(var i = 0; i < iterations; i++) {
        f_glob_obj();
        result = global_obj.a;
    }
    console.log('f_glob_obj', (performance.now() - start).toFixed(2));
    /////////////////////
    &#13;
    &#13;
    &#13;

    以下是我在Firefox和Chrome上的典型运行:

       Function       | Firefox 47 | Chrome 52
    ------------------+------------+----------
    1. f_arr          |    501     |   206 
    2. f_obj          |    202     |   172
    3. f_obj_inplace  |     17     |    69
    4. f_glob_reg     |     19     |    34
    5. f_glob_obj     |     22     |    31
    

    结果应该带有一些误差范围,因为它们在每次运行时都会有所不同,具体取决于PC负载。

    这些结果表明,普通对象解决方案(2)比阵列解决方案(1)更快。特别是在Firefox上,阵列解决方案要慢得多。

    我们还可以看到,使用就地变异的全局变量(3,4和5)可以获得巨大的性能提升。将全局对象作为函数参数传递时,Chrome仍然会有一些性能损失(3),而对于Firefox,它几乎没有任何区别。

    但更重要的是,从最后两次测量中,我们看到无类型的全局变量解与原始类型的全局变量解一样快。

    说明

    由于已知原因,不建议在函数中使用全局变量:它使代码更少模块化,更难以阅读并且更难调试。

    如果你想获得每次在同一个地方存储数据的性能优势,那么将变量作为参数传递给函数:这样做的性能损失似乎在可接受的范围内(尽管Firefox似乎处理得更好)用它而不是Chrome)。

    该函数可能总是写入相同的全局变量,从而破坏以前的结果。如果你最终将结果复制到别处以避免被覆盖,那么你将再次增加开销。

    使用类型化数组并不是性能的决定性因素。

    结论

    上述结果和考虑因素促使我建议采用第三种方式:

    function f_obj_inplace(obj) {
        var a = 1;
        var b = 2;
        obj.a = a
        obj.b = b;
    }
    

    您不会对全局变量进行丑陋的直接访问,也不会使用类型化数组。但是你确实使用了就地变异和普通对象而不是数组。

答案 1 :(得分:2)

我想把它扔出去,因为它非常有趣......

如果您可以返回一个可用于获取两个原始值的数字,该怎么办?

如果返回时间是您唯一关心但计算时间不是,您可能需要考虑cantor配对功能

这是一个函数,它将两个整数作为输入,并返回一个值,该值稍后可用于反转过程并检索两个原始数字。

自然地,实现这一目标所需的计算会产生某种开销,但如果它的关键返回时间可能是有用的。

你可以在网上找到例子,这里是php的一个实现:

https://gist.github.com/hannesl/8031402

ETA - 上面的链接也链接到javascript实现。我从未尝试过这样说实话,但我记得读过它,很有趣。

答案 2 :(得分:0)

第一种方法更专业。全局变量只应在应用程序中谨慎使用。可以在本地存在而不影响结果的对象应该在本地环境中使用。

全局变量搞乱了我们的记忆,我们的范围。您可以通过主流浏览器上的开发人员控制台检查这两种方法的脚本执行时间,没有人会告诉您第一种方法更可取。

JavaScript是一种C-Like编程语言。因此,让我们看一下它的C原点如何表现:

当我们创建变量时,我们的变量占用寄存器中的地址,计算机将其位置的本地存储保存在内存中以便以后访问它。

另一方面,当我们创建数组时,我们只存储第一个元素(指针)的地址。我们的计算机不知道两个变量的位置。仅指向第一个变量的位置。这样可以更快地访问我们的数据并消耗更少的资源。

想象一下我们想从函数中返回100个数字的情况。通过我们的论证,我们可以清楚地看到返回数组的效率要高得多。

请注意:这两种方法都很好而且快速。我只是说明了为什么只要可以考虑第一种方法的原因。