ビット演算と四則演算の話の前に


ビット演算は早すぎる
ビット演算は四則演算より速いか


この話,バージョン/環境/OS/ブラウザ/その他,
非常に様々なものが複雑に絡んでて,アンタッチャブルな臭いがします.
ちなみに自分の環境での結果は,flashrod氏のコメント欄の通りすがりの人と
同じような感じ.cellfusion氏の追記も大体似た感じかな.


というわけで,多分アンタッチャブルとは思いつつもいろいろ試してみた.
まず環境.

Intel(R) Pentium(R) M processor 1.20GHz
Windows XP/StandAlone/WIN 9,0,15,0/debugger

ここで多分重要なのはdebuggerである事.
最近,ブラウザ上のJITコンパイラによる最適化って,
実はdebuggerではそんなに効いてないんじゃないかなと感じています.
ByteArrayとuint/int演算を多用したメタボールのコードは,
なぜかブラウザの方が早かったので.もちろん限りなく無根拠ですが.
ので,本当はブラウザ上で測定してみたいけど,安定した状態でちゃんと測定するには,
かなりしっかりとした測定コードを書いて,根気良く計らないといけないのでムリ.
だから,今回の結果はあくまでも参考程度にとどめておくべきでしょう.


さてソース.

package {
    import flash.display.Sprite;
    import flash.utils.getTimer;
    import flash.system.Capabilities;
    
    public class Optimize extends Sprite {
        public function Optimize() {
            var startTime:int;
            var i:int;
            
            var n0:int = 1234;
            var n1:int = 8765;
            var n2:int = 1;
            var na:int;
            
            var u0:uint = 1234;
            var u1:uint = 8765;
            var u2:uint = 1;
            var ua:uint;

            var f0:Number = 1234.5678;
            var f1:Number = 8765.4321;
            var fa:Number;
            
            trace(Capabilities.os+"/"+Capabilities.playerType+"/"+Capabilities.version+"/"+(Capabilities.isDebugger?"debugger":"normal"));
            trace();

/* ここで色々試す */
            s();  for(i=0;i<10000000;i++) { na = n0 + n1; }     e("i=i+i;      ");
            s();  for(i=0;i<10000000;i++) { na = n0 * n1; }     e("i=i*i;      ");
            s();  for(i=0;i<10000000;i++) { na = n0 << n2; }    e("i=i<<i;     ");
/* ここで色々試す */
            
            function s()           : void { startTime = getTimer(); }
            function e(str:String) : void { trace(str+(getTimer() - startTime)); }
        }
    }
}


今回やってみた範囲では測定の関数化による差は出ませんでした.


コンパイラオプション.

<compiler>
    <show-actionscript-warnings>true</show-actionscript-warnings>
    <show-binding-warnings>true</show-binding-warnings>
    <show-deprecation-warnings>true</show-deprecation-warnings>
    <optimize>true</optimize>
    <debug>false</debug>
    <incremental>false</incremental>
</compiler>

今回やってみた範囲ではによる差は出ませんでした.



さてまず,お題のビット演算と四則演算について

s();  for(i=0;i<10000000;i++) { na = n0 + n1; }     e("i=i+i;      ");
s();  for(i=0;i<10000000;i++) { na = n0 * n1; }     e("i=i*i;      ");
s();  for(i=0;i<10000000;i++) { na = n0 << n2; }    e("i=i<<i;     ");
s();  for(i=0;i<10000000;i++) { na = n0 & n2; }     e("i=i&i;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 + f1; }     e("f=f+f;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 * f1; }     e("f=f*f;      ");

i=i+i; 82
i=i*i; 188
i=i< i=i&i; 72
f=f+f; 81
f=f*f; 80


乗算は遅めで,加算とビット演算は同程度で速め.
だがしかし,その速度は浮動小数と同程度.
うーん,つまりint型の乗算はいまだに浮動小数でやってて,
加算とビット演算はint型のまま計算してるんだけど,
浮動小数演算も十分早いよ,みたいな事?
この時点で既にかなりあやしい.


さらにあやしいのが

s();  for(i=0;i<10000000;i++) { ua = u0 + u1; }     e("u=u+u;      ");
s();  for(i=0;i<10000000;i++) { ua = u0 * u1; }     e("u=u*u;      ");
s();  for(i=0;i<10000000;i++) { ua = u0 << u2; }    e("u=u<<u;     ");
s();  for(i=0;i<10000000;i++) { ua = u0 & u2; }     e("u=u&u;      ");

u=u+u; 221
u=u*u; 650
u=u< u=u&u; 73


uint型意味不明.あえて考察しません.
ちなみに乗算結果がオーバーフローするようにして測定すると,
uintもintも2500ms程かかります.演算オーバーフローは厳禁みたい.


さて,先ほどは計算は浮動小数じゃないかって疑問が出たので
基本に立ち返って代入を測定.

s();  for(i=0;i<10000000;i++) { na = u0; }          e("i=u;        ");
s();  for(i=0;i<10000000;i++) { ua = n0; }          e("u=i;        ");
s();  for(i=0;i<10000000;i++) { na = f0; }          e("i=f;        ");
s();  for(i=0;i<10000000;i++) { fa = n0; }          e("f=i;        ");
s();  for(i=0;i<10000000;i++) { fa = u0; }          e("f=u;        ");
s();  for(i=0;i<10000000;i++) { ua = f0; }          e("u=f;        ");

i=u; 71
u=i; 70
i=f; 138
f=i; 62
f=u; 220
u=f; 134


これで多少はuint型の挙動も説明付くのかな?
>uint->floatが一番重いので,u=u*uは圧倒的に重くなった.
>u=u+uは,int型への変換後,計算自体はi=i+iで行われた.
>uint型ビット演算はint型と全く同じなので同速.
結局,乗算が入った時点で浮動小数でやってると考えると,
上記数値は一応筋は通るみたいに見える.多分.


んじゃ,次にコンパイラは数値の書き方によってどういう風に変換するのか.

s();  for(i=0;i<10000000;i++) { na = n0 + 2; }      e("i=i+2;      ");
s();  for(i=0;i<10000000;i++) { na = n0 + 0xabcd; } e("i=i+0xabcd; ");
s();  for(i=0;i<10000000;i++) { na = n0 + 0.2; }    e("i=i+0.2;    ");
s();  for(i=0;i<10000000;i++) { ua = u0 + 2; }      e("u=u+2;      ");
s();  for(i=0;i<10000000;i++) { ua = u0 + 0xabcd; } e("u=u+0xabcd; ");
s();  for(i=0;i<10000000;i++) { ua = u0 + 0.2; }    e("u=u+0.2;    ");
s();  for(i=0;i<10000000;i++) { fa = f0 + 2; }      e("f=f+2;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 + 0xabcd; } e("f=f+0xabcd; ");
s();  for(i=0;i<10000000;i++) { fa = f0 + 0.2; }    e("f=f+0.2;    ");
s();  for(i=0;i<10000000;i++) { na = n0 * 2; }      e("i=i*2;      ");
s();  for(i=0;i<10000000;i++) { na = n0 * 0xabcd; } e("i=i*0xabcd; ");
s();  for(i=0;i<10000000;i++) { na = n0 * 0.2; }    e("i=i*0.2;    ");
s();  for(i=0;i<10000000;i++) { ua = u0 * 2; }      e("u=u*2;      ");
s();  for(i=0;i<10000000;i++) { ua = u0 * 0xabcd; } e("u=u*0xabcd; ");
s();  for(i=0;i<10000000;i++) { ua = u0 * 0.2; }    e("u=u*0.2;    ");
s();  for(i=0;i<10000000;i++) { fa = f0 * 2; }      e("f=f*2;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 * 0xabcd; } e("f=f*0xabcd; ");
s();  for(i=0;i<10000000;i++) { fa = f0 * 0.2; }    e("f=f*0.2;    ");

i=i+2; 73
i=i+0xabcd; 71
i=i+0.2; 168
u=u+2; 65
u=u+0xabcd; 76
u=u+0.2; 417
f=f+2; 81
f=f+0xabcd; 80
f=f+0.2; 78
i=i*2; 184
i=i*0xabcd; 188
i=i*0.2; 174
u=u*2; 436
u=u*0xabcd; 477
u=u*0.2; 455
f=f*2; 80
f=f*0xabcd; 79
f=f*0.2; 79


基本的には,加算はint/uint型として認識できればint型,乗算はfloat型.
って事でおk?


おまけ,一応全組み合わせやってみた.

s();  for(i=0;i<10000000;i++) { fa = n0 + n1; }     e("f=i+i;      ");
s();  for(i=0;i<10000000;i++) { na = f0 + f1; }     e("i=f+f;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 + n0; }     e("f=f+i;      ");
s();  for(i=0;i<10000000;i++) { na = f0 + n0; }     e("i=f+i;      ");
trace();
s();  for(i=0;i<10000000;i++) { na = u0 + u1; }     e("i=u+u;      ");
s();  for(i=0;i<10000000;i++) { ua = n0 + n1; }     e("u=i+i;      ");
s();  for(i=0;i<10000000;i++) { na = n0 + u0; }     e("i=i+u;      ");
s();  for(i=0;i<10000000;i++) { ua = n0 + u0; }     e("u=i+u;      ");
trace();
s();  for(i=0;i<10000000;i++) { fa = u0 + u1; }     e("f=u+u;      ");
s();  for(i=0;i<10000000;i++) { ua = f0 + f1; }     e("u=f+f;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 + u0; }     e("f=f+u;      ");
s();  for(i=0;i<10000000;i++) { ua = f0 + u0; }     e("u=f+u;      ");
trace();
s();  for(i=0;i<10000000;i++) { fa = n0 * n1; }     e("f=i*i;      ");
s();  for(i=0;i<10000000;i++) { na = f0 * f1; }     e("i=f*f;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 * n0; }     e("f=f*i;      ");
s();  for(i=0;i<10000000;i++) { na = f0 * n0; }     e("i=f*i;      ");
trace();
s();  for(i=0;i<10000000;i++) { fa = u0 * u1; }     e("f=u*u;      ");
s();  for(i=0;i<10000000;i++) { ua = f0 * f1; }     e("u=f*f;      ");
s();  for(i=0;i<10000000;i++) { fa = f0 * u0; }     e("f=f*u;      ");
s();  for(i=0;i<10000000;i++) { ua = f0 * u0; }     e("u=f*u;      ");
trace();
s();  for(i=0;i<10000000;i++) { na = u0 * u1; }     e("i=u*u;      ");
s();  for(i=0;i<10000000;i++) { ua = n0 * n1; }     e("u=i*i;      ");
s();  for(i=0;i<10000000;i++) { na = n0 * u0; }     e("i=i*u;      ");
s();  for(i=0;i<10000000;i++) { ua = n0 * u0; }     e("u=i*u;      ");
trace();

f=i+i; 79
i=f+f; 159
f=f+i; 80
i=f+i; 174

i=u+u; 221
u=i+i; 74
i=i+u; 222
u=i+u; 227

f=u+u; 427
u=f+f; 154
f=f+u; 277
u=f+u; 424

f=i*i; 79
i=f*f; 156
f=f*i; 81
i=f*i; 175

f=u*u; 452
u=f*f; 156
f=f*u; 292
u=f*u; 468

i=u*u; 648
u=i*i; 198
i=i*u; 454
u=i*u; 459


結局のところ,一応,今回の測定だけ見ると

  • 基本的には,加算/ビット演算はfloat型が入ってなければint型で計算.
  • 乗算が入っていると,強制float型演算.
  • そもそもint型演算とfloat型演算は大差無し.
  • uint型はビット演算で無い限り,int型/float型どちらかに変換して計算.
  • uint->float/float->uintはかなり重い.
  • float->intもそこそこ重い.

って感じでしょうか....
バイナリハックができればもっと話はスマートなんだと思いますが,
そんな知識は無いのでとりあえずこんな感じで終わり.


ただ,そもそもこのデバッガによる評価自体に意味があるのかは疑問なんですが.