パスワードを忘れた? アカウント作成
10398694 story
Firefox

FirefoxにおけるJavaScriptの実行速度、C++にまた一歩迫る 80

ストーリー by hylom
asm.jsは楽しそうだ 部門より
あるAnonymous Coward 曰く、

Firefoxでは特定のJavaScriptコードを高速に実行する「asm.js」という仕組みがある。高速とは言えども、今年頭の時点ではネイティブコードの半分程度の速度でしか実行できなかったのだが、コード翻訳ツールおよびJavaScriptエンジンの最適化を進めた結果、現在ではネイティブコードと比べて1.5倍遅い(同じ処理の実行に1.5倍の時間がかかる)程度にまで改善が進んだそうだ(Mozilla Hacksマイナビニュース)。

最適化は多くの局面で行われたとのことだが、一例としてあげられているのは浮動小数点演算で、64ビットのdouble型演算の代わりに、可能な限り32ビットのfloat型の演算を使用することで、大幅にその演算速度が向上したという。

この方法はほかのブラウザのJavaScript実行環境にも適用可能で、今後さらにほかのブラウザでの実行速度が高速になる可能性があるとのこと。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • asm.jsはGCも全くせずAOTでコンパイルしてるわけだから、あっそうとしか思えん…
    時間を掛けてコンパイルできない分、凝った最適化が出来無い中での結果なんで、そこは凄いなと思う。

    ただ、これでWebGL使って全画面のアプリを作れば、HTMLとか全て無視した普通のアプリが動いちゃうんだろうな。
    まぁネイティブアプリが簡単にマルチプラットフォーム化できればありがたいけど。
    • by Anonymous Coward

      「あっそうとしか思えん」からが長い!

    • by Anonymous Coward

      GC云々とAOTの関連性がよくわからないので不躾な私にもわかるようにもう少し詳しく教えていただけませんか

    • by Anonymous Coward

      前半の文章は全く誤りです。
      asm.jsでもGCは働きます。ただArrayBufferをメモリ空間かのように使うことでGCに頼らない方法を推奨しており、emscriptenも積極的にそういったコードを出力します。ただそれはasm.jsとは直接関係なく普通のJSにおいてもかなり強力なテクニックです。

      後半はその通りです。
      本質的にJITの性質を引き出すことで、部分的にネイティブかそれ以上のパフォーマンスを引き出せるケースはいくつもあります。
      今回の実行時間1.5倍というのは、ネイティブのコードから機械的に変換されたコードでの場合であり、JSの限界性能ではないということです。
      おそらく現在でもそれを考慮すると、JSはネイティブに匹敵する速度になったと言って良いと思われます。

      • あなたこそ少し誤解している風に見受けられます。

        asm.jsは普通に人が書いたJavaScriptを実行する為のエンジンは使用していません。
        まったく別のエンジンを使用して動かしているので、一般的なJSエンジンの挙動はしないと思われます。
        そもそも、CやC++から変換したコードの実行が主なのでGCする必要がないのに、あえて速度を落としてまで
        するんですかね?

        > JSはネイティブに匹敵する速度
        いや、全然違います…
        asm.jsはその名の通りアセンブラ代わりに使っているだけです。
        (ただ、Firefox以外のエンジンでも普通に動かせるところが画期的ですが)
        で、AOTのコンパイラが実行時に全部機械語に変換して実行しているだけなんです。

        親コメント
  • by Anonymous Coward on 2013年12月25日 19時34分 (#2517778)

    指標となるネイティブコードはどの程度最適化されたものなのだろうか。

    • by Anonymous Coward

      同じなんでないの、asm.jsは手で書く言語じゃないから。C++のコードから変換してるはず。

      • by Anonymous Coward

        いや、そのコードがそもそもどれくらい最適化されてるかって話では?
        冗長な部分を残したコードをasm.jsが最適化したら速くなるわけですし……

        • by Anonymous Coward on 2013年12月25日 21時44分 (#2517822)

          ベンチマークなんだから、最適化もへったくれもないと思うんだが。

          例えば、こういうやつでしょ。
          https://github.com/joelgwebber/bench2d [github.com]

          http://j15r.com/blog/2013/04/25/Box2d_Revisited [j15r.com]

          によると、4月の時点でbox2dを実行した場合、
          emscripten版をFirefoxで実行すると、gccの12倍。(つまり、性能は1/12)
          Box2dWeb版をFirefoxで実行すると、gccの14倍。(つまり、性能は1/14)
          asm.js版ををFirefoxで実行すると1.9倍。(つまり、性能は半分)

          しかし、今回の記事では、
          https://hacks.mozilla.org/wp-content/uploads/2013/12/asm1.5b.png [mozilla.org]
          (おそらく)同じベンチでgccの1.5倍くらい(つまり、性能は0.7倍程度)までになった、と。面白いのはFirefoxよりネイティブclangの方が遅いっていう事実だと思う。つまり、clangはコンパイラのくせに浮動小数点の最適化をやっていないのではないかという疑惑が……

          親コメント
        • by Anonymous Coward

          どこでもかしこでもインラインアセンブラを使ったりするほどはしてないと思うけど、
          歴史のあるベンチの歴史のあるコードなんだから無駄なんて無いよ
          多分githubで公開されてるから探してみれば?

  • by Anonymous Coward on 2013年12月25日 20時36分 (#2517801)

    組み込みやインラインアセンブラでSIMDが使えるのがC言語の利点なので、
    ド・ノーマルのコードと比べても意味は無いんだよね。

    • by Anonymous Coward

      それはC++の利点でCの利点じゃない。
      C++11ならラムダ式の中にインラインアセンブリを直書きして、関数の戻り値にして投げるとか訳わからん事が出来るぞ。

      Cの利点はリーナスが言ってる様に、プログラマがメモリの確保と解放を完全に制御できること。
      GCとかは裏で何やるか分からないからカーネルとか作れんのだ、クラスのあるC++もメモリ管理を隠蔽可能だから不可

      • # むー、C++11に移行するかなぁ...。

        クラスのあるC++もメモリ管理を隠蔽可能だから不可

        そうでもない。
        C++で普通にアクセスする分には変な挙動はないよ。

        中身を別のモジュールに渡したりする必要があるようなときにだけextern "C"ででも囲ってCの流儀にすればいい。
        それと汎用の(クラスなしの)new/delete/new[]/delete[]を作っておけばたいていのものはいける。

        追記するなら、例外は使うならフレーム作る必要があるし、場合によっては未定義の仮想関数のためのシンボルを定義しておく必要があるくらいか。

        親コメント
    • by Anonymous Coward

      まともな高級言語なら(たとえばFORTRANとか)自動ベクトル化くらいしてくれるでしょ。人間様が自分で頭を捻らなくてはならないから低級言語は面倒だ。

    • by Anonymous Coward

      JSにもSIMDが入ることになってるよ。
      SM,V8での実装及びasm.jsでの対応は来年。

  • by Anonymous Coward on 2013年12月25日 22時24分 (#2517847)

    数値演算の精度を端折って高速化したと。

    浮動小数点演算で、64ビットのdouble型演算の代わりに、可能な限り32ビットのfloat型の演算を使用することで、大幅にその演算速度が向上したという。

    数値演算の精度が問題になる場面で、不具合が起きなければいいが・・・Officeスウィート系のWebアプリは怖くて使えんなあ。

    • インテル系のCPUなら倍精度もハードウェアで処理するから
      単精度に置き換えたところで速度がそれほど変わるとは
      思えないのですが。

      親コメント
      • by Anonymous Coward
        SIMD、つまり、SSExに乗っけるときは、倍精度と単精度では、同時に計算できる量が倍違うので、そのまま速度に効いてきます。
        従来のFPUを(というか、FPUとして)使い続けるなら、確かに、むしろ、倍精度の方が速いことの方が多いでしょう。
        • by Anonymous Coward

          確か前見たデータでは数%から数百%まで差があったけど下回ってるのは無かったな

      • by Anonymous Coward

        floatだとデータ量がdoubleの半分になる。
        今どきのCPUだとメモリアクセスが一番遅い。加算も減算も分岐も数クロックで終わる、だがメモリーアクセスは数十クロック以上かかる。
        なんで今どきの最適化はメモリーアクセスを減らす以外対して効かない。

    • by Anonymous Coward on 2013年12月26日 10時37分 (#2518065)

      JSの数値型はdoubleだから、今まではfloatをエミュレートするのにコストがかかってたのよ

      例えば
      A:float = B:float + 1.0
      は普通のJSだと本来
      A = ToFloat(ToDouble(B) + 1.0:double)
      とすることになる

      実はこれなら今まででも
      A = B:float + 1.0:float
      と最適化できてたんけど

      A:float = B:float + C:float + 1.0
      みたいに少し違うパターンだと途端に最適化していいのかどうかの判断が難しくなってくるので、
      本当は結果が変わらないのでしていい場合もできてなかった

      なのでこの度は、ネイティブからの「コード変換時に」floatで演算していい部分を
      「doubleをfloatの精度を持ったdoubleに丸める」という効果を持ったMath.fround関数で囲むことで明示的に示し
      float演算での無駄を100%無くすことに成功したよって話

      擬似コードはこんな感じになる
      A:float = Math.fround(B:float + C:float) + 1.0
      これはつまり
      F:float = B:float + C:float
      A:float = F:float + 1.0:float
      と同じ意味になるから最初に挙げたパターンの判断のみで最適化可能
      A = B:float + C:float + 1.0:float

      親コメント
    • by Anonymous Coward

      明らかに処理の内容を変えて「高速化」というのは疑問を感じますね。
      それで、C++に迫ったといわれてもなんか違う。

      • by Anonymous Coward

        世の中の最適化を全否定だな。答えが一緒なら処理なんて関係ないでしょ。

        • by Anonymous Coward

          型が単精度から倍精度に変わるんだよ?
          常に答えが一緒になるとは限らないでしょ。

          んで、最適化によって値が変わる可能性があるから、Cとかにはvolatileが用意されてるわけで。
          JavaScriptにvolatileに相当するものは私の知る限りは無いはず。
          そういう言語で値が変わるかも知れない最適化ってのはちょっと問題ある気がする。

          JavaScriptの仕様的にはオーバーフローしたら倍精度に勝手に変換するみたいだけど、そこら辺どうなんだろ。
          仕様に合わせて内部処理を最適化しただけって話ならいいんだけど。

          • 数値計算の世界では、倍精度なら精度が高くて、単精度なら精度が低い、なんて思ってると違ってる場合もあります。

            単純な数値表現の精度より、計算アルゴリズムの方が計算誤差に大きく影響するケースが少なくないためです。 例えば、単純に合計求めるだけでも値に大きさのばらつきがあると倍精度で愚直に加算するより、単精度でもソートしてから加算するアルゴリズムのほうが精度が高くなります。歴史がある数値計算ライブラリなどはそういう考慮が詰まっていて、倍精度で作られた下手なライブラリより単精度のライブラリの方が精度が高くなる、なって状況が簡単に起こります

            まあ、数値計算の精度ってのは使いドコロに適したものを正しく使うってのが、このストーリーでも大事ってことですな。

            --
            親コメント
          • by Anonymous Coward

            ごめん、*可能な限り*単精度、って書いてあるね。
            なら問題無いのか。

            • Mozilla Hacksの元記事確認してみたが、最適化しても大丈夫っぽい。

              まず、a,bをfloatとして、(float)((double)a + (double)b) と a + b は、倍精度で加算するか単精度で加算するかの違いがあるが、必ず同じ結果になるらしい。さらにこれと同じようなことが、加減乗除や平方根などで成り立つよう。

              (倍精度で計算するとdouble roundingになるので単精度計算よりむしろ悪い結果を返してしまうコーナーケースが考えられるため、非自明な性質な気がするが、入力が単精度の値であることを使ってきちんと見ていけば計算結果にコーナーケースが出ないのと言えるのだと思う。気になるなら参照されてる論文見れば良いかと)

              なお、a + b + cについて同じようなことを考えると、これは正しくない。たとえば、(1 + 2^-24) + 2^-24とか。

              で、JavaScriptでは倍精度浮動小数点数しか存在しない。そして、浮動小数点数同士の計算は倍精度として行わないといけない。そこで、Math.froundという、倍精度浮動小数点数を単精度相当の値に変換する関数を用意する。これはES6に入るんだそうな(ただしfroundが無くてもFloat32Arrayに入れるというイディオムで一応変換可能)。

                  a = Math.fround(a);
                  b = Math.fround(b);
                  x = Math.fround(a + b);

              とすれば、先の (float)((double)a + (double)b) と同じことなので、安全に単精度数同士の加算として扱える。

              なので、emscriptenに、floatへ代入する際にガシガシMath.fround()で囲ってもらえば高速化できると。

              # 型とはなんだったのか

              親コメント
              • by Anonymous Coward

                普通に入力する値は1.5とか2.0とかが大半なんで、ポインタ算をしなければfloatで最適化した方がいい、ってことなんだろうね。C++だってautofloatみたいな型をでっちあげて、必要な時だけfloatとしてふるまうようにすれば、ずっと高速化できるってことだと思う。やっぱりポインタ算は鬱陶しいだろうけど。

            • by Anonymous Coward

              asm.jsは基本的に別の別の型付き言語からの変換するのでfloatを使用されている箇所はそのままfloatの精度で計算できるんだよね。

              • by Anonymous Coward

                ただし、JavaScriptを解釈しているときは一応常にdoubleの精度で計算したのと同じ結果を出すことを保障できないとマズイよな。
                だからコンパイル時にアノテーションがいるのかな。なんというかasm.jsがトランスレータに責任を押し付けただけのようでもあるけど。

              • by Anonymous Coward

                Math.froundが適切に使われればdoubleと結果は変わらないし、asm.jsじゃなくてJITでも同等の最適化ができる。

          • by Anonymous Coward

            んで、最適化によって値が変わる可能性があるから、Cとかにはvolatileが用意されてるわけで。

            違うけど?

            • by Anonymous Coward

              ん?
              冗長性の排除も最適化の一種だよね?
              それによってはき出す値が一定しないなら「最適化によって値が変わる」って言えると思ってたんだけど、違うかな。
              特にポインタの排除なんか、Cだと値が不定になるわけでしょ?

              実際にvolatileが必要になるケースに出会った事がなくて資料上で把握していた知識なので、認識が間違ってたらその部分を指摘してもらえるととても嬉しい。

              • それはむしろ例えばgccの-ffloat-storeとか-fexcess-precisionとか-ffast-math-funsafe-math-optimizationsとかのオプションで変えるような動作なんじゃないか?丸めるとか丸めないとか、そんな類の動作の違いで結果として動かす環境によって下の方のビットが違ったり違わなかったりとかするわけだけど。

                volatileのは最適化じゃなくて何かのバグだろう。volatileはその有無で計算内容が変わるわけじゃない。

                親コメント
              • by Anonymous Coward

                volatileはその有無で計算内容が変わるわけじゃない。

                volatileの有無で演算の順序が変わり計算結果に影響がでる可能性は当たり前に考えられるよ。

              • ちょっと自信ないけど、

                volatile double hoge;

                ってあったとすると、hogeへのアクセスは必ずメモリを読み書きするんだよね?
                doubleはメモリ上では64ビットだけど、x86のFPUレジスタは80ビットだから、
                誤差が出るかもしれないし出ないかもしれない。
                私もよくわかりませんが、どなたか検証してくれないかな。

                親コメント
              • by Anonymous Coward on 2013年12月26日 14時39分 (#2518214)

                ウチの環境(core2duo Cygwin gcc 4.7.3)で試してみた結果が↓


                #include <stdio.h>
                #include <limits.h>

                int main()
                {
                        const int x = INT_MAX;
                        {
                                double e = 1.0;
                                const double m = 1.0 + 1.0 / x;
                                int i;
                                for (i = 0; i < x; i++) {
                                        e *= m;
                                }
                                printf("(1.0 + 1.0 / %d) ** %d = %1.20lf\n", x, x, e);
                        }
                        {
                                volatile double e = 1.0;
                                const double m = 1.0 + 1.0 / x;
                                int i;
                                for (i = 0; i < x; i++) {
                                        e *= m;
                                }
                                printf("(1.0 + 1.0 / %d) ** %d = %1.20lf\n", x, x, e);
                        }
                        return 0;
                }


                $ gcc -O2 e.c && ./a.exe
                (1.0 + 1.0 / 2147483647) ** 2147483647 = 2.71828182656034833542
                (1.0 + 1.0 / 2147483647) ** 2147483647 = 2.71828182653240091327

                親コメント
              • お、誤差出ましたか。

                http://homepage1.nifty.com/herumi/prog/prog90.html [nifty.com]

                ↑によると、fnstcwという命令で、演算精度を変更できるらしいです。
                同じマシンでも、コンパイラによって、設定が異なるとか。

                親コメント
              • ほうほう。
                一致してるのが10進で10桁(+一の位が2なので1bit)だから(10/log2)+1でだいたい33bitsとちょっと(?)ってとこか。
                「オプションを何も付けてなきゃ計算精度も格納精度も一緒だから誤差は出ない」かと思ってた。
                オプションつけるとまた違うんだろうなー。

                親コメント
          • by Anonymous Coward

            型が単精度から倍精度に変わるんだよ?
            常に答えが一緒になるとは限らないでしょ。

            それ以前に浮動小数点演算は精度に関係なく答が一緒になる保証などない。

            • by Anonymous Coward

              釣り?
              入力が同じなら出力は同じだよ?

              • by Anonymous Coward

                マクロな話してるんでしょ
                例えばsinとかは同じdoubleでも実装や言語によって全然違うし

              • by Anonymous Coward

                同じソースプログラム、同じ動作環境でも、コンパイラの最適化やx87とSSEの違いなんかで簡単に違う結果になるよ。

  • by Anonymous Coward on 2013年12月26日 9時13分 (#2518009)

    どう言い訳しても声豚です。本当にありがとうございました。

    # 申し訳ない気持ちでいっぱいです。

  • 最新のFirefox 26.0 でクラッシュが頻繁に発生するんですけど。
    脆弱性ですか? 困るんですよね。

typodupeerror

私はプログラマです。1040 formに私の職業としてそう書いています -- Ken Thompson

読み込み中...