coding, photo, plant and demo

*v8の最適化をシンセの観点から調べる

tech v8 js 20120603 011126

Google Moog Synthesizerの衝撃

ちょっと前、googleのトップページがmoogのシンセもどきになっていたのは衝撃だった。

コードを見てみると、Flashで音を出しているわけではなく、基本すべてJavaScript上で行われているようだ。
ベースとなる波形を作っている部分はこんな感じ。前半がエンベロープの計算で、後半が鋸歯とかを作ってる。
  1. 1var Db = function(a, b, c, d, e) {
  2. 2 var b = b.outputBuffer, f = b.getChannelData(1), h = b.getChannelData(0);
  3. 3 a.G && !a.c && (a.c = new Float32Array(b.length));
  4. 4 var m; m = a.p;
  5. 5 a.e || (m = 0);
  6. 6 m = 55 * a.w * Math.pow(2, (m + -4) / 12);
  7. 7 var p; p = Cb;
  8. 8 a.e || (p = 8);
  9. 9 p = Math.abs(a.m * (p - 1)) + 1;
  10. 10 p = 0 <= a.m ? p : 1 / p;
  11. 11 for (var k, z, r, t = 0; t < b.length; ++t) {
  12. 12 k = a.R;
  13. 13 switch (k.b) {
  14. 14 case 0:
  15. 15 k.a += k.m;
  16. 16 1 <= k.a && (k.a = 1, k.b = 1);
  17. 17 break;
  18. 18 case 1:
  19. 19 k.a -= k.l;
  20. 20 k.a <= k.e && (k.a = k.e, k.b = 2);
  21. 21 break;
  22. 22 case 3:
  23. 23 k.a -= k.h, 0 >= k.a && (k.a = 0, k.b = 4)
  24. 24 }
  25. 25 k = k.a;
  26. 26 var A = a;
  27. 27 r = p;
  28. 28 var $a = a, w;
  29. 29 w = a;
  30. 30 var Da = m;
  31. 31 !w.l || w.a === j || 0 >= w.h || Math.abs(w.a - Da) <= Math.abs(w.k) ? w = w.a = w.n = Da : (w.n != Da && (w.k = (Da - w.a) / (w.h / w.g), w.n = Da), w.a += w.k, w = w.a);
  32. 32 r *= !c || !$a.va ? w : (0.75 * c[t] + 1.25) * w;
  33. 33 $a = 2 / r;
  34. 34 A.b > $a && (A.b -= $a);
  35. 35 r = A.b * r / 2;
  36. 36 A.b += A.g;
  37. 37 switch (a.t) {
  38. 38 case 0:
  39. 39 z = 4 * ((0.5 < r ? 1 - r : r) - 0.25);
  40. 40 break;
  41. 41 case 1:
  42. 42 z = (z = 0.5 > r) ? 4 * r - 1 : -2 * r + 1;
  43. 43 break;
  44. 44 case 2:
  45. 45 z = 2 * (r - 0.5);
  46. 46 break;
  47. 47 case 3:
  48. 48 z = -2 * (r - 0.5);
  49. 49 break;
  50. 50 case 4:
  51. 51 z = 0.5 > r ? 1 : -1;
  52. 52 break;
  53. 53 case 5:
  54. 54 z = r < 1 / 3 ? 1 : -1;
  55. 55 break;
  56. 56 case 6:
  57. 57 z = 0.25 > r ? 1 : -1
  58. 58 }
  59. 59 a.G && (a.c[t] = z * a.v);
  60. 60 k *= z * a.r;
  61. 61 0 == d ? h[t] = k : 1 == d ? h[t] += k : (h[t] = (h[t] + k) / e, f && (f[t] = h[t]))
  62. 62 }
  63. 63};

そういえば、shader toyを見た時にも思ったのだけど、実はシンセサイザはJavaScriptで書くのが一番理に適うのでは。オシレータやらフィルタやらを柔軟に接続できるようプログラム上で工夫しなくとも、適当に数式を書いてevalすればJITでnativeに落とし込まれ実行されるとなれば、シンセの自由度では圧勝。速度面でも、JIT抜きのnativeのシンセサイザよりも、直接的に最終的な数式をnativeに落とせるJIT有りのJavaScriptが将来的には高速になる可能性がある。

何も工夫しないループ

JavaScriptが最もシンセ向きな言語の可能性は、ある。それは分かった。じゃあ現状はどうなのか、v8で簡単にテストしてみた。

まずは、音の基本、sin波(の7次多項式近似)をレンダリングするコードで見てみよう。シンセでも、サイン波はパイプオルガンの音を作ったり、FM変調の基礎になったりする重要な波形です。ただし、ホントは-PIからPIの範囲でしか近似できないにも関わらずクリッピングしてないため、このコード自体の実用価値はないので悪しからず。

  1. 1function bench(buf,len) {
  2. 2 for(var i = 0; i < len; i++) {
  3. 3 var w = 2 * Math.PI * i / 44100;
  4. 4 var x = w * 440;
  5. 5 buf[i] = x -x*x*x/2/3 + x*x*x*x*x/2/3/4/5 - x*x*x*x*x*x*x/2/3/4/5/6/7;
  6. 6 }
  7. 7}
  8. 8var size = 10000;
  9. 9var buf = new Array(size);
  10. 10bench(buf, size);
これをv8に食わせるとタイトループはこんなコードになる。

なお、使ったコマンドは
d8_g --print_code test.js
shell_gだとtyped arrayが使えないのでd8_gを使ったほうが良いっぽい。
自分が使ったのはr11554のtrunkを
scons d8 mode=debug verbose=on disassembler=on
でビルドしたものです。

0xb7b37446   390  894dc0         mov [ebp+0xc0],ecx
0xb7b37449   393  3bca           cmp ecx,edx
0xb7b3744b   395  0f8d49010000   jnl 730  (0xb7b3759a)
0xb7b37451   401  3b252c1a4d08   cmp esp,[0x84d1a2c]
0xb7b37457   407  0f82ea010000   jc 903  (0xb7b37647)
0xb7b3745d   413  817fff21cc0045 cmp [edi+0xff],0x4500cc21    ;; object: 0x4500cc21 <Map(elements=1)>
0xb7b37464   420  0f85dc2bad7e   jnz 0x3660a046              ;; deoptimization bailout 7
0xb7b3746a   426  f20f2af9       cvtsi2sd xmm7,ecx
0xb7b3746e   430  f20f105db8     movsd xmm3,[ebp+0xb8]
0xb7b37473   435  f20f59df       mulsd xmm3,xmm7
0xb7b37477   439  f20f5eda       divsd xmm3,xmm2
0xb7b3747b   443  f20f59dc       mulsd xmm3,xmm4
0xb7b3747f   447  0f28fb         movaps xmm7,xmm3
0xb7b37482   450  f20f59fb       mulsd xmm7,xmm3
0xb7b37486   454  f20f59fb       mulsd xmm7,xmm3
0xb7b3748a   458  0f28d7         movaps xmm2,xmm7
0xb7b3748d   461  f20f5ed1       divsd xmm2,xmm1
0xb7b37491   465  f20f5ed5       divsd xmm2,xmm5
0xb7b37495   469  0f28e3         movaps xmm4,xmm3
0xb7b37498   472  f20f5ce2       subsd xmm4,xmm2
0xb7b3749c   476  f20f59fb       mulsd xmm7,xmm3
0xb7b374a0   480  f20f59fb       mulsd xmm7,xmm3
0xb7b374a4   484  0f28d7         movaps xmm2,xmm7
0xb7b374a7   487  f20f5ed1       divsd xmm2,xmm1
0xb7b374ab   491  f20f5ed5       divsd xmm2,xmm5
0xb7b374af   495  f20f5ed6       divsd xmm2,xmm6
0xb7b374b3   499  f20f1075b0     movsd xmm6,[ebp+0xb0]
0xb7b374b8   504  f20f5ed6       divsd xmm2,xmm6
0xb7b374bc   508  f20f58e2       addsd xmm4,xmm2
0xb7b374c0   512  f20f59fb       mulsd xmm7,xmm3
0xb7b374c4   516  f20f59fb       mulsd xmm7,xmm3
0xb7b374c8   520  f20f5ef9       divsd xmm7,xmm1
0xb7b374cc   524  f20f5efd       divsd xmm7,xmm5
0xb7b374d0   528  f20f105588     movsd xmm2,[ebp+0x88]
0xb7b374d5   533  f20f5efa       divsd xmm7,xmm2
0xb7b374d9   537  f20f5efe       divsd xmm7,xmm6
0xb7b374dd   541  f20f105da0     movsd xmm3,[ebp+0xa0]
0xb7b374e2   546  f20f5efb       divsd xmm7,xmm3
0xb7b374e6   550  f20f104da8     movsd xmm1,[ebp+0xa8]
0xb7b374eb   555  f20f5ef9       divsd xmm7,xmm1
0xb7b374ef   559  f20f5ce7       subsd xmm4,xmm7
0xb7b374f3   563  f20f11a570ffffff movsd [ebp+0xffffff70],xmm4
0xb7b374fb   571  89d8           mov eax,ebx
0xb7b374fd   573  8178ff21c50045 cmp [eax+0xff],0x4500c521    ;; object: 0x4500c521 <Map(elements=0)>
0xb7b37504   580  0f850c000000   jnz 598  (0xb7b37516)
0xb7b3750a   586  bb41c50045     mov ebx,0x4500c541          ;; object: 0x4500c541 <Map(elements=2)>
0xb7b3750f   591  89c2           mov edx,eax
0xb7b37511   593  e80ab5fdff     call TransitionElementsSmiToDouble  (0xb7b12a20)    ;; debug: position 150
                                                             ;; code: BUILTIN
0xb7b37516   598  8b45c8         mov eax,[ebp+0xc8]
0xb7b37519   601  8178ff41c50045 cmp [eax+0xff],0x4500c541    ;; object: 0x4500c541 <Map(elements=2)>
0xb7b37520   608  0f852a2bad7e   jnz 0x3660a050              ;; deoptimization bailout 8
0xb7b37526   614  8b4807         mov ecx,[eax+0x7]
0xb7b37529   617  8b500b         mov edx,[eax+0xb]
0xb7b3752c   620  f6c201         test_b dl,0x1
0xb7b3752f   623  0f8528010000   jnz 925  (0xb7b3765d)
0xb7b37535   629  d1fa           sar edx,1
0xb7b37537   631  8b5dc0         mov ebx,[ebp+0xc0]
0xb7b3753a   634  3bda           cmp ebx,edx
0xb7b3753c   636  0f83182bad7e   jnc 0x3660a05a              ;; deoptimization bailout 9
0xb7b37542   642  f20f108d70ffffff movsd xmm1,[ebp+0xffffff70]
0xb7b3754a   650  660f2ec9       ucomisd xmm1,xmm1
0xb7b3754e   654  0f8b08000000   jpo 668  (0xb7b3755c)
0xb7b37554   660  f20f100d309d4c08 movsd xmm1,[0x84c9d30]
0xb7b3755c   668  f20f114cd907   movsd [ecx+ebx*8+0x7],xmm1
0xb7b37562   674  83c301         add ebx,0x1
0xb7b37565   677  89d9           mov ecx,ebx
0xb7b37567   679  89c3           mov ebx,eax
0xb7b37569   681  8b55cc         mov edx,[ebp+0xcc]
0xb7b3756c   684  8b7dc4         mov edi,[ebp+0xc4]
0xb7b3756f   687  f20f104d80     movsd xmm1,[ebp+0x80]
0xb7b37574   692  f20f105598     movsd xmm2,[ebp+0x98]
0xb7b37579   697  f20f106590     movsd xmm4,[ebp+0x90]
0xb7b3757e   702  f20f10ad78ffffff movsd xmm5,[ebp+0xffffff78]
0xb7b37586   710  f20f107588     movsd xmm6,[ebp+0x88]
0xb7b3758b   715  f20f105da0     movsd xmm3,[ebp+0xa0]
0xb7b37590   720  f20f107da8     movsd xmm7,[ebp+0xa8]
0xb7b37595   725  e9acfeffff     jmp 390  (0xb7b37446)

とりあえず、頭で思い描いていた理想的コードとは掛け離れた効率の悪いコードが出てきたー!

まず、全然定数を外に追い出せてなくね?
と思ったけど、floatは演算順序が変わると量子化誤差のせいで結果が変わるので、致し方ないのだった。gccでもこれは同じだけど、-funsafe-math-optimizationsと指定すれば、その辺り無視してくれる選択肢はある点で違う。
JSの場合はこういったケースは丁寧に予め人力で最適化しておくのが必須だと分かる。

次に、実際の計算以外のコストが高い。573以降はすべて型チェックのコストだと思う。v8はタイトループは型決め打ちでコードを生成して、決め打った型とは違うものが出てきたらdeoptimizationとやらを呼んで、コードを作り直すようだ。その場合、当然もの凄いロスが発生する。

最後に、687からの定数用のSSEレジスタを復帰させているところ。これ毎回やる必要があるのかな?call TransitionElementsSmiToDoubleが実行されると、レジスタの値が保証されないということだろうか。詳しく追ってないのでよく分かりません。

おまけ。SSEを使ってるけど、ベクトル化はしてないです。てことでfpuで計算するのと速度は変わんないと思う。あと、初めて知ったんだけど、SSEって浮動小数の積和演算ないんですね。こりゃびっくりだ。何考えてチップ設計したんだ?浮動小数だと複雑になっちゃうからかな。

定数を追い出すなど最適化したループ

前節の反省を生かし、手動で最適化してみる。
  1. 1function bench(buf,len) {
  2. 2 var c3 = -1.0/2/3;
  3. 3 var c5 = 1.0/2/3/4/5;
  4. 4 var c7 = -1.0/2/3/4/5/6/7;
  5. 5 var cw = 2.0 * Math.PI / 44100 * 440;
  6. 6 for(var i = 0; i < len; i++) {
  7. 7 var x = cw * i;
  8. 8 var x2 = x*x;
  9. 9 buf[i] = x * (1 + x2 * (c3 + x2 * (c5 + x2 * c7)));
  10. 10 }
  11. 11}
  12. 12var size = 10000;
  13. 13var buf = new Array(size);
  14. 14bench(buf, size);

すると、実際の計算部分は大分スリムになった。
しかし型チェックは変わらず。
0x59636d5b   635  897dc0         mov [ebp+0xc0],edi
0x59636d5e   638  3bfa           cmp edi,edx
0x59636d60   640  0f8de7000000   jnl 877  (0x59636e4d)
0x59636d66   646  3b252c1a4d08   cmp esp,[0x84d1a2c]
0x59636d6c   652  0f8288010000   jc 1050  (0x59636efa)
0x59636d72   658  f20f2af7       cvtsi2sd xmm6,edi
0x59636d76   662  0f28fb         movaps xmm7,xmm3
0x59636d79   665  f20f59fe       mulsd xmm7,xmm6
0x59636d7d   669  0f28f7         movaps xmm6,xmm7
0x59636d80   672  f20f59f7       mulsd xmm6,xmm7
0x59636d84   676  0f28ce         movaps xmm1,xmm6
0x59636d87   679  f20f59cc       mulsd xmm1,xmm4
0x59636d8b   683  f20f1055b8     movsd xmm2,[ebp+0xb8]
0x59636d90   688  f20f58d1       addsd xmm2,xmm1
0x59636d94   692  0f28ce         movaps xmm1,xmm6
0x59636d97   695  f20f59ca       mulsd xmm1,xmm2
0x59636d9b   699  f20f1055b0     movsd xmm2,[ebp+0xb0]
0x59636da0   704  f20f58d1       addsd xmm2,xmm1
0x59636da4   708  f20f59f2       mulsd xmm6,xmm2
0x59636da8   712  0f28cd         movaps xmm1,xmm5
0x59636dab   715  f20f58ce       addsd xmm1,xmm6
0x59636daf   719  0f28d7         movaps xmm2,xmm7
0x59636db2   722  f20f59d1       mulsd xmm2,xmm1
0x59636db6   726  f20f115590     movsd [ebp+0x90],xmm2
0x59636dbb   731  89d8           mov eax,ebx
0x59636dbd   733  8178ff21c5a043 cmp [eax+0xff],0x43a0c521    ;; object: 0x43a0c521 <Map(elements=0)>
0x59636dc4   740  0f850c000000   jnz 758  (0x59636dd6)
0x59636dca   746  bb41c5a043     mov ebx,0x43a0c541          ;; object: 0x43a0c541 <Map(elements=2)>
0x59636dcf   751  89c2           mov edx,eax
0x59636dd1   753  e84abcfdff     call TransitionElementsSmiToDouble  (0x59612a20)    ;; debug: position 406
                                                             ;; code: BUILTIN
0x59636dd6   758  8b45d8         mov eax,[ebp+0xd8]
0x59636dd9   761  8178ff41c5a043 cmp [eax+0xff],0x43a0c541    ;; object: 0x43a0c541 <Map(elements=2)>
0x59636de0   768  0f858832cde7   jnz 0x4130a06e              ;; deoptimization bailout 11
0x59636de6   774  8b4807         mov ecx,[eax+0x7]
0x59636de9   777  8b500b         mov edx,[eax+0xb]
0x59636dec   780  f6c201         test_b dl,0x1
0x59636def   783  0f851b010000   jnz 1072  (0x59636f10)
0x59636df5   789  d1fa           sar edx,1
0x59636df7   791  8b5dc0         mov ebx,[ebp+0xc0]
0x59636dfa   794  3bda           cmp ebx,edx
0x59636dfc   796  0f837632cde7   jnc 0x4130a078              ;; deoptimization bailout 12
0x59636e02   802  f20f104d90     movsd xmm1,[ebp+0x90]
0x59636e07   807  660f2ec9       ucomisd xmm1,xmm1
0x59636e0b   811  0f8b08000000   jpo 825  (0x59636e19)
0x59636e11   817  f20f100d309d4c08 movsd xmm1,[0x84c9d30]
0x59636e19   825  f20f114cd907   movsd [ecx+ebx*8+0x7],xmm1
0x59636e1f   831  83c301         add ebx,0x1
0x59636e22   834  89df           mov edi,ebx
0x59636e24   836  8b75d4         mov esi,[ebp+0xd4]
0x59636e27   839  89c3           mov ebx,eax
0x59636e29   841  8b55dc         mov edx,[ebp+0xdc]
0x59636e2c   844  8b4de0         mov ecx,[ebp+0xe0]
0x59636e2f   847  f20f1065a0     movsd xmm4,[ebp+0xa0]
0x59636e34   852  f20f105da8     movsd xmm3,[ebp+0xa8]
0x59636e39   857  f20f1055b0     movsd xmm2,[ebp+0xb0]
0x59636e3e   862  f20f104db8     movsd xmm1,[ebp+0xb8]
0x59636e43   867  f20f106d98     movsd xmm5,[ebp+0x98]
0x59636e48   872  e90effffff     jmp 635  (0x59636d5b)

typed array導入

この型チェックは何とかならんのか、とFloat32Arrayを使ってみる。
  1. 1function bench(buf,len) {
  2. 2 var c3 = -1.0/2/3;
  3. 3 var c5 = 1.0/2/3/4/5;
  4. 4 var c7 = -1.0/2/3/4/5/6/7;
  5. 5 var cw = 2.0 * Math.PI / 44100 * 440;
  6. 6 for(var i = 0; i < len; i++) {
  7. 7 var x = cw * i;
  8. 8 var x2 = x*x;
  9. 9 buf[i] = x * (1 + x2 * (c3 + x2 * (c5 + x2 * c7)));
  10. 10 }
  11. 11}
  12. 12var size = 10000;
  13. 13var buf = new Float32Array(size);
  14. 14bench(buf, size);

おお!かなりまともになりました。理想にかなり近づいた。頭の悪いCコンパイラの出力と言っても過言ではない程度には良いです。動的型付け言語でここまでできれば上出来かと。

0x5e036a00   640  3bc2           cmp eax,edx
0x5e036a02   642  0f8d6b000000   jnl 755  (0x5e036a73)
0x5e036a08   648  3b252c1a4d08   cmp esp,[0x84d1a2c]
0x5e036a0e   654  0f823b010000   jc 975  (0x5e036b4f)
0x5e036a14   660  f20f2ac8       cvtsi2sd xmm1,eax
0x5e036a18   664  0f28fb         movaps xmm7,xmm3
0x5e036a1b   667  f20f59f9       mulsd xmm7,xmm1
0x5e036a1f   671  0f28cf         movaps xmm1,xmm7
0x5e036a22   674  f20f59cf       mulsd xmm1,xmm7
0x5e036a26   678  0f28d1         movaps xmm2,xmm1
0x5e036a29   681  f20f59d5       mulsd xmm2,xmm5
0x5e036a2d   685  f20f105dbc     movsd xmm3,[ebp+0xbc]
0x5e036a32   690  f20f58da       addsd xmm3,xmm2
0x5e036a36   694  0f28d1         movaps xmm2,xmm1
0x5e036a39   697  f20f59d3       mulsd xmm2,xmm3
0x5e036a3d   701  0f28dc         movaps xmm3,xmm4
0x5e036a40   704  f20f58da       addsd xmm3,xmm2
0x5e036a44   708  f20f59cb       mulsd xmm1,xmm3
0x5e036a48   712  0f28d6         movaps xmm2,xmm6
0x5e036a4b   715  f20f58d1       addsd xmm2,xmm1
0x5e036a4f   719  f20f59fa       mulsd xmm7,xmm2
0x5e036a53   723  3bc1           cmp eax,ecx
0x5e036a55   725  0f831d369def   jnc 0x4da0a078              ;; deoptimization bailout 12
0x5e036a5b   731  f20f5ac7       cvtsd2ss xmm0,xmm7
0x5e036a5f   735  f30f110487     movss [edi+eax*4],xmm0
0x5e036a64   740  83c001         add eax,0x1
0x5e036a67   743  f20f105db4     movsd xmm3,[ebp+0xb4]
0x5e036a6c   748  f20f1055bc     movsd xmm2,[ebp+0xbc]
0x5e036a71   753  eb8d           jmp 640  (0x5e036a00)

gccの場合

では普通にCで書いてみましょう。
  1. 1void bench(float* buf, int len) {
  2. 2 float c3 = -1.0f/2/3;
  3. 3 float c5 = 1.0f/2/3/4/5;
  4. 4 float c7 = -1.0f/2/3/4/5/6/7;
  5. 5 float cw = 2.0f * M_PI / 44100 * 440;
  6. 6 for(int i = 0; i < len; i++) {
  7. 7 float x = cw * i;
  8. 8 float x2 = x*x;
  9. 9 buf[i] = x * (1 + x2 * (c3 + x2 * (c5 + x2 * c7)));
  10. 10 }
  11. 11}

g++ -O3 -c bench.c

  18:   89 45 fc                mov    %eax,-0x4(%ebp)
  1b:   db 45 fc                fildl  -0x4(%ebp)
  1e:   d8 c9                   fmul   %st(1),%st
  20:   d9 c0                   fld    %st(0)
  22:   d8 c9                   fmul   %st(1),%st
  24:   d9 05 04 00 00 00       flds   0x4
  2a:   d8 c9                   fmul   %st(1),%st
  2c:   d8 05 08 00 00 00       fadds  0x8
  32:   d8 c9                   fmul   %st(1),%st
  34:   d8 25 0c 00 00 00       fsubs  0xc
  3a:   de c9                   fmulp  %st,%st(1)
  3c:   d8 05 10 00 00 00       fadds  0x10
  42:   de c9                   fmulp  %st,%st(1)
  44:   d9 1c 81                fstps  (%ecx,%eax,4)
  47:   83 c0 01                add    $0x1,%eax
  4a:   39 d0                   cmp    %edx,%eax
  4c:   75 ca                   jne    18 <_Z5benchPfi+0x18>
そりゃ普通に書いたらこうなるよね。単純かつ速い。
objdumpしたせいでちょっと分かりにくいけど、fldsやfadds,fsubsのoperandの即値はアドレスです。しかし、この定数値のロードはループの外に追い出せるんだがな…まだ甘い。

とはいえ、forから抜けるジャンプをv8は字面通りループの開始時に判定していたけど、gccはちゃんと後ろに持ってきてjmp命令を1個省くなど人間なら当然やるだろう最適化をやっている。偉い。

gcc with sse

同じコードを
g++ -O3 -msse2 -mfpmath=sse -c bench.c
とするとどうなるか。

なんと!4並列で実行してくれてる!
ループ回数が多い場合は間違いなく最速でしょう。
 120:	0f 5b cb             	cvtdq2ps %xmm3,%xmm1
 123:	89 d0                	mov    %edx,%eax
 125:	83 c2 01             	add    $0x1,%edx
 128:	c1 e0 04             	shl    $0x4,%eax
 12b:	39 ca                	cmp    %ecx,%edx
 12d:	0f 59 ce             	mulps  %xmm6,%xmm1
 130:	66 0f fe df          	paddd  %xmm7,%xmm3
 134:	0f 28 d1             	movaps %xmm1,%xmm2
 137:	0f 59 d1             	mulps  %xmm1,%xmm2
 13a:	0f 28 c2             	movaps %xmm2,%xmm0
 13d:	0f 59 c5             	mulps  %xmm5,%xmm0
 140:	0f 58 c4             	addps  %xmm4,%xmm0
 143:	0f 59 c2             	mulps  %xmm2,%xmm0
 146:	0f 5c 05 40 00 00 00 	subps  0x40,%xmm0
 14d:	0f 59 c2             	mulps  %xmm2,%xmm0
 150:	0f 58 05 50 00 00 00 	addps  0x50,%xmm0
 157:	0f 59 c1             	mulps  %xmm1,%xmm0
 15a:	0f 29 04 07          	movaps %xmm0,(%edi,%eax,1)
 15e:	72 c0                	jb     120 <_Z5benchPfi+0x120>
ただ、ここでは省きましたが、前後に端数となる部分をスカラーで計算するコードが入るため、全体としてかなり大きくなってオーバーヘッドも増えてます。シンセの場合定期的にエンベロープやLFO等の更新が必要なため、一度に大量にループすることはないことを考えると、オーバーヘッドを避けるために入力データは16byte align済みで4の倍数個決め打ちとして、asmで書いてしまうに越したことはないですね。
という、やっぱasm最強という当たり前すぎる帰着で終了。

まとめ

以上、シンセっぽいタイトループの最適化についてv8で簡単に調べて結果わかったこと。

  • JavaScriptで高速化したいなら、数式は手動で最適化しておくこと
  • 必ずtyped arrayを使うこと
  • 簡単なループでもC(gcc)の出力と比べるとv8はまだまだ野暮ったい。つまりこれからまだまだ速くなるかも!?

13.05.18 09:23 guest
これは強い最適化がされた後のコードですか?
長いループの場合途中からそうなりますが
14.07.10 00:39 mtm
すみません、今頃気づきました。
bailoutがあるので最適化済みという認識です。
opt codeじゃなくて最適化前のfull codeだとツッコミを入れようがないくらい遅いコードが出てきた気がします。
コメントする