Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2010-05-27

imageDataの読み書き高速化の記事を検証

05:53

とても怪しいかったので検証。

元ベンチマークでやってること。

  • 240x320 の canvas から getImageData で imageData を取ってきて、
  • imageData の data プロパティ (CanvasPixelArray) に値を書き込んで、
  • putImageData で canvas に戻す。

image という変数が imageData で、imageData という変数が CanvasPixelArray なのでとてもややこしい。


以下の4つを比較している。

1. global scoping, standard access

こんな感じ。

var image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // グローバル
...
var loop = function(){
    ...
    while(--i){
        image.data[4*i+0] = grey;
        image.data[4*i+1] = grey;
        image.data[4*i+2] = grey;
        image.data[4*i+3] = 255;
    }
    context.putImageData(image, 0, 0);
    ...
};

image.data へのアクセスは、(1) グローバル変数を探し、(2) プロパティを探す、という2段階になっている。

これを見ると、Canvas はまったく関係なくて、単に ECMAScript の話のように思う。

結果。

Firefox 3.68ms
Chromium 646ms
Safari 47ms
Opera 10.55ms

Chromium だけ遅い。


2. global scoping, detached access

上とほとんど同じだけど、image.data をローカル変数に格納している。

var image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // グローバル
...
var loop = function(){
    ...
    var imageData = image.data; // ローカルにキャッシュ
    while(--i){
        imageData[4*i+0] = grey;
        imageData[4*i+1] = grey;
        imageData[4*i+2] = grey;
        imageData[4*i+3] = 255;
    }
    context.putImageData(image, 0, 0);
    ...
};

imageData のアクセスは、ローカル変数を探すだけ。global と唄ってる割にはグローバル変数へのアクセスを計測してるわけではない。

Firefox 3.68ms
Chromium 65ms
Safari 46ms
Opera 10.53ms

当然どれも速い。


3. namespace scoping, standard access

var test = (function(){
    ...
    var image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // グローバルではない
    ...
    var loop = function(){
        ...
        while(--i){
            image.data[4*i+0] = grey;
            image.data[4*i+1] = grey;
            image.data[4*i+2] = grey;
            image.data[4*i+3] = 255;
        }
        context.putImageData(image, 0, 0);
        ...
    };
...

image.data へのアクセスは、(1) 一段上のスコープの変数を探し、(2) プロパティを探す、という2段階。

Firefox 3.676ms
Chromium 644ms
Safari 47ms
Opera 10.54ms

Firefox と Chromium が遅い。

最初のやつと比べてみると、Firefox はグローバル変数へのアクセスは速いけど、セミローカル (一つ上のスコープ) な変数へのアクセスは遅いらしい。

そこで、ループ内をこういうふうに書き換えてみる。

        var image_ = image; // セミローカル変数をローカルにキャッシュ
        while(--i){
            image_.data[4*i+0] = grey;
            image_.data[4*i+1] = grey;
            image_.data[4*i+2] = grey;
            image_.data[4*i+3] = 255;
        }
        context.putImageData(image_, 0, 0);

すると、Firefox で 76ms から 36ms になった。他のブラウザでは変わらず。

ちなみに1番目のテストでも同じような変更をしてみたけど、これはどのブラウザも効果がなかった。

また、image をセミローカルではなくグローバルに出してみると、これまた Firefox で 36ms になった。やはり Firefox はグローバル変数のアクセスはセミローカルは遅いということが分かる。他のブラウザは変わらず。


4. namespace scoping, detached access

var test = (function(){
    var image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // グローバルではない
    ...
    var loop = function(){
        ...
        var imageData = image.data; // ローカルにキャッシュ
        while(--i){
            imageData[4*i+0] = grey;
            imageData[4*i+1] = grey;
            imageData[4*i+2] = grey;
            imageData[4*i+3] = 255;
        }
        context.putImageData(image, 0, 0);
        ...
    };
...

imageData はローカル変数なので、2番目と似ている。

Firefox 3.634ms
Chromium 65ms
Safari 47ms
Opera 10.54ms

Firefox は2番目より遅かった。むしろ3番目の修正版に近い結果。

image をグローバルに出して測ってみたけど効果なかった。(image にはアクセスしてないので当然)


imageData を使わずに検証

var image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

とかやってる部分を

var image = {data:(function(){var n=SCREEN_HEIGHT*SCREEN_WIDTH,a=Array(n);for(var i=0;i<n;i++)a[i]=0;return a;}())};

として、単純な配列にしてみた。それから setImageData は消した。試したけど setImageData はパフォーマンスに影響は無かったし。

すると、Chrome だけは1番目と3番目のテスト (遅かったやつ) が 3ms〜4ms になって2番目や4番目と同じぐらいになった。

他のブラウザでは imageData を使うのとほとんど変わらなかった。


結論

結論に迷う。確かに Chromium では image.data でアクセスするよりローカル変数に入れたほうがだいぶ速くなる。Opera や Safari もちょっとだけ速くなる (が、誤差の範囲だし普通の Array アクセスにも同じことが言えるので取りたててどうということはない)。

Firefox は…何と言ったらいいかよくわからない。単純にスコープが深いと遅いというわけでもないみたい。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20100527
 |