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

テストを開始するには、画像をクリックします。結果がコンソールに表示されます。

次の 3 つの異なるバージョンがあります。

元のコード:

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

それを最適化する私の試みの1つ:

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

最後の 2 つの唯一の違いは、'index' 変数の計算です。そして驚いたことに、最適化されたバージョンはほとんどのブラウザー (Opera を除く) で遅くなります。

個人テストの結果(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
    

掘り下げた後、スコープルックアップのために主にローカル変数にアクセスするのが通常の良い習慣のようです。最適化されたバージョンは 1 つのローカル変数のみを呼び出すため、関連するさまざまな操作に加えて、複数の変数とオブジェクトを呼び出すハイブリッド コードよりも高速になるはずです。

では、なぜ「最適化された」バージョンが遅いのでしょうか?

一部の JavaScript エンジンが Optimized バージョンを最適化していないことが原因ではないかと考えていましたが--trace-opt、chrome で使用したところ、すべてのバージョンが V8 で適切にコンパイルされているようです。

この時点で、私は少し無知で、誰かが何が起こっているのか知っているのだろうか?

このページでさらにいくつかのテストケースも行いました。

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

4

2 に答える 2

1

ばかげているように聞こえますが、Math.whatever()呼び出しを最適化して JS エンジン用にインライン化するのは難しいかもしれません。可能な限り、同じ結果を得るために (関数呼び出しではなく) 算術演算を優先してください。

次の 4 番目のテストを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 で次の数値を返します。

  • オリジナル - 2311ms
  • リファクタリング - 112ms
  • ハイブリッド - 2371ms
  • p01 - 112ms
于 2012-09-19T09:07:18.533 に答える
0

いくつかの基本的な手法を使用すると、パフォーマンスを高度に最適化できます。

  1. 複数のループをループで実行する場合は、次を使用します。

    while(i--){/*ここにいくつかのコード*/}

...ここで、iは0より大きい値です。

  1. 計算を最小限に抑えるために、変数を適切にキャッシュ/ローカライズします。大規模な計算の場合、これは計算の一部を抽象化の適切なレイヤーに配置することを意味します。

  2. 変数の再利用(再初期化のオーバーヘッドは、大量のデータ処理で問題になる可能性があります)。注:これは悪いプログラミング設計の原則ですが、優れたパフォーマンスの原則です!

  3. プロパティの深さを減らします。object.propertyを使用すると、「object_propertyvalue」を含む変数と比較してパフォーマンスが低下します。

これらの原則を使用すると、パフォーマンスを向上させることができます。さて、高レベルから、この関数を派生させた記事を見ると、いくつかの点で欠陥がありました。したがって、あなたが述べた1行だけでなく、完全な機能を実際に最適化するには、次のようにします。

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 に答える