11

****澄清**:我不是在寻找最快的代码或优化。我想了解为什么一些似乎没有优化或优化的代码实际上通常运行得更快。

短版

为什么是这个代码:

var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;

比这个性能更好?

var index = Math.floor(ref_index) * 4;

长版

本周,Impact js 的作者发表了一篇关于一些渲染问题的文章:

http://www.phoboslab.org/log/2012/09/drawing-pixels-is-hard

在文章中,有一个函数的来源,通过访问画布中的像素来缩放图像。我想建议一些传统的方法来优化这种代码,以便在加载时缩放会更短。但是在测试之后,我的结果大部分时间都比原始功能最差。

猜测这是 JavaScript 引擎正在做一些智能优化,我试图更多地了解发生了什么,所以我做了一堆测试。但是我的结果很混乱,我需要一些帮助来了解发生了什么。

我在这里有一个测试页面:

http://www.mx981.com/stuff/resize_bench/test.html

jsPerf:http: //jsperf.com/local-variable-due-to-the-scope-lookup

要开始测试,请单击图片,结果将出现在控制台中。

共有三个不同的版本:

原代码:

for( var y = 0; y < heightScaled; y++ ) {
    for( var x = 0; x < widthScaled; x++ ) {
        var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
        var indexScaled = (y * widthScaled + x) * 4;
        scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
        scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
        scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
        scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
    }
}

jsPerf:http: //jsperf.com/so-accessing-local-variable-doesn-t-improve-performance

我优化它的尝试之一:

var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
    for( var x = 0; x < widthScaled; x++ ) {
        var index = Math.floor(ref_index) * 4;
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];

        ref_index+= ref_step;
    }
}

jsPerf:http: //jsperf.com/so-accessing-local-variable-doesn-t-improve-performance

相同的优化代码,但每次都重新计算索引变量(混合)

var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
    for( var x = 0; x < widthScaled; x++ ) {
        var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
        scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];

        ref_index+= ref_step;
    }
}

jsPerf:http: //jsperf.com/so-accessing-local-variable-doesn-t-improve-performance

最后两个的唯一区别是'index'变量的计算。令我惊讶的是,优化版本在大多数浏览器(除了歌剧)中都比较慢。

个人测试结果(不是 jsPerf 测试):

  • 歌剧

    Original:  8668ms
    Optimized:  932ms
    Hybrid:    8696ms
    
  • 铬合金

    Original:  139ms
    Optimized: 145ms
    Hybrid:    136ms
    
  • 苹果浏览器

    Original:  433ms
    Optimized: 853ms
    Hybrid:    451ms
    
  • 火狐

    Original:  343ms
    Optimized: 422ms
    Hybrid:    350ms
    

在挖掘之后,由于范围查找,似乎一个通常的好习惯是主要访问局部变量。因为优化后的版本只调用一个局部变量,所以除了涉及的各种操作之外,调用多个变量和对象的混合代码应该更快。

那么为什么“优化”版本更慢呢?

我认为这可能是因为某些 JavaScript 引擎没有优化优化版,因为它不够热,但是--trace-opt在 chrome 中使用后,似乎所有版本都被 V8 正确编译。

在这一点上,我有点无能为力,想知道是否有人会知道发生了什么?

我还在这个页面中做了一些更多的测试用例:

http://www.mx981.com/stuff/resize_bench/index.html

4

2 回答 2

1

听起来很傻,这些Math.whatever()调用对于 JS 引擎的优化和内联可能很棘手。只要有可能,最好使用算术运算(而不是函数调用)来获得相同的结果。

将以下第四个测试添加到http://www.mx981.com/stuff/resize_bench/test.html

// Test 4
console.log('- p01 -');
start = new Date().getTime();
for (i=0; i<nbloop; i++) {
  var index = 0;
  var ref_indexScaled = 0
  var ref_step=1/scale;


  for( var y = 0; y < heightScaled; y++ ) {
    for( var x = 0; x < widthScaled; x++ ) {
      var z= index<<2;
      scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
      scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
      scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
      scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];

      index+= ref_step;
    }
  }
}
end = new Date().getTime();
console.log((end-start)+'ms');

在 Opera Next 中产生以下数字:

  • 原始 - 2311 毫秒
  • 重构 - 112ms
  • 混合 - 2371ms
  • p01 - 112 毫秒
于 2012-09-19T09:07:18.533 回答
0

使用一些基本技术可以高度优化性能:

  1. 在循环中运行多个循环时,请使用:

    while (i--) { /* 这里有一些代码 */ }

... 其中 i 是一个大于 0 的值。

  1. 适当地缓存变量/本地化变量以最小化计算。对于较大的计算,这意味着将部分计算放在正确的抽象层。

  2. 重用变量(重新初始化开销可能成为大量数据处理的问题)。注意:这是一个糟糕的编程设计原则,但却是一个很好的性能原则!

  3. 减少属性深度。与仅包含“object_propertyvalue”的变量相比,使用 object.property 会降低性能。

使用这些原则,您可以获得更好的性能。现在从高层次来看,查看您从中派生此功能的文章,它在几个方面存在缺陷。因此,要真正优化全部功能,而不仅仅是您所说的那一行:

function resize_Test5( img, scale ) {
    // Takes an image and a scaling factor and returns the scaled image

    // The original image is drawn into an offscreen canvas of the same size
    // and copied, pixel by pixel into another offscreen canvas with the 
    // new size.

    var widthScaled = img.width * scale;
    var heightScaled = img.height * scale;

    var orig = document.createElement('canvas');
    orig.width = img.width;
    orig.height = img.height;
    var origCtx = orig.getContext('2d');
    origCtx.drawImage(img, 0, 0);
    var origPixels = origCtx.getImageData(0, 0, img.width, img.height);

    var scaled = document.createElement('canvas');
    scaled.width = widthScaled;
    scaled.height = heightScaled;
    var scaledCtx = scaled.getContext('2d');
    var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );

    // optimization start
    var old_list = origPixels.data;
    var image_width = img.width;
    var h = heightScaled;
    var w = widthScaled;
    var index_old;
    var index_new;
    var h_scale;
    var new_list = [];
    var pre_index_new;

    while(h--){
        h_scale = Math.floor(h / scale) * image_width;
        pre_index_new = h * widthScaled;
        while(w--){
            index_old = (h_scale + Math.floor(w / scale)) * 4;
            index_new = (pre_index_new + w) * 4;
            new_list[ index_new ]     = old_list[ index_old ];
            new_list[ index_new + 1 ] = old_list[ index_old + 1 ];
            new_list[ index_new + 2 ] = old_list[ index_old + 2 ];
            new_list[ index_new + 3 ] = old_list[ index_old + 3 ];
        }
    }
    scaledPixels.data = new_list;
    // optimization stop

    scaledCtx.putImageData( scaledPixels, 0, 0 );
    return scaled;
}
于 2012-09-23T03:35:38.937 回答