Hatena::Groupjavascript

JavaScriptで遊ぶよ

|

2011-11-07

JS.next

06:34

Brendan Eich さんと Jeremy Ashkenas さん(CoffeeScript の作者)のプレゼンを見た。頭の4分の1ぐらいが Eich さん。

これを見てぼんやりと思ったことを書いてく。将来のことについては、当たるも八卦当たらぬも八卦、話半分で読んでくれたら幸いです。


ArrayType, StructType

プレゼンの10分目あたりから。

f:id:edvakf:20111108044218p:image

Harmony Wiki より。

const Point2D = new StructType({ x: uint32, y: uint32 });
const Color = new StructType({ r: uint8, g: uint8, b: uint8 });
const Pixel = new StructType({ point: Point2D, color: Color });
 
const Triangle = new ArrayType(Pixel, 3);
 
let t = new Triangle([{ point: { x:  0, y: 0 }, color: { r: 255, g: 255, b: 255 } },
                      { point: { x:  5, y: 5 }, color: { r: 128, g: 0,   b: 0   } },
                      { point: { x: 10, y: 0 }, color: { r: 0,   g: 0,   b: 128 } }]);
...
http://wiki.ecmascript.org/doku.php?id=harmony:binary_data

例えば Canvas のプログラムだと、500px * 500px = 250000 で25万ぐらいのループが出てくることがある。WebGL だと普通は JS 側でループすることはないけど、代わりに 3D モデルの頂点数が数万ぐらいのオーダーで出てくる。それで 60fps のスピードを出そうと思ったら、フレーム間の処理を 15ms に抑えないといけない。

それぐらいの単位だと JS のオブジェクトプロパティを弄ったりするのがネックになるので、本気で対処しようとすると↓こういうレベルで最適化しないといけなくなる。(著者は V8 作ってる Vyacheslav Egorov さん。おもしろかったので一読をおすすめします。)

var blocks = [];

function Block(size) {
    this.size = size;
    this.buf = new ArrayBuffer(this.size);
    this.i32 = new Int32Array(this.buf);
    this.f64 = new Float64Array(this.buf);
}

function malloc(N) {
    if (blocks[N] && blocks[N].length) return blocks[N].pop();
    return new Block(N);
}

function free(addr) {
    (blocks[addr.size] || (blocks[addr.size] = [])).push(addr);
}

function createArrayOfPoints(n) {
    var points = malloc(24 * n);
    var points_i32 = points.i32;
    var points_f64 = points.f64;
    for (var i1 = 0, i2 = 1, i = 0; i < n; i++, i1 += 6, i2 += 3) {
        points_i32[i1]     = /* n */ i; 
        points_f64[i2]     = /* x */ i * 0.1 + 0.1; 
        points_f64[i2 + 1] = /* y */ i * 0.9 - 0.1;
    }
    return points;
}
http://blog.mrale.ph/post/12396216081/the-trap-of-the-performance-sweet-spot

この記事を読んで最初に思ったのは、

パフォーマンスのネックになるところは手で JS 書くんじゃなくて C で書いて JS にコンパイルする時代がもうすぐ来るな。C じゃなくてもそれに特化した言語でもいい。Dart はそっちを目指してるんだろうか。

http://twitter.com/#!/edvakf/status/133422069626044416

ということ。(Dart のことはほとんど知らないです。すいません)

Emscripten みたいに JS にコンパイルする言語などはそのぶんオーバーヘッドがかかるんじゃないかと思われるけど、実は逆。静的型付け言語から機械で生成した JS に手書きの JS が勝つことは難しくなってくるんじゃないかと思う。

JS はもう十分に速くなった。もうどれも同じ。なんてことはない。JS は他の言語をその上に作れるような言語になるべきだし、そのためにはまだ十分ではない。

まあこんなこと僕が書かなくてもすごい人たちが前から言ってたことなんですが。

でも、JavaScriptでそんな言語処理系は書かないよね?

そうとも言い切れないという思いがします。JavaScriptは年々高速になっており、nativeコードとの差が殆どなくなれば、その可能性はいくらでもあると思っています。JavaScriptでFlashプレイヤーを実装してしまうくらいの心躍る強者が現れることを密かに願っています。

JavaScriptのswitch文の速度評価記事への補足 - 風と宇宙とプログラム

僕は、JavaScript は21世紀のC言語だと思っているのですが、仕様として用意すべき物はC言語同様、プリミティブな物にすべきだと思います。

WebSoocketではなくTCPSocketが欲しい! - yukobaのブログ

JS.nextはどういうものであるべきか

さっきの鬼最適化について最初の感想を書いた数分後に、

と思ったけど、普通に JS で書いて Object.freeze したら JIT で勝手に最適化してくれそうな気もするな。

http://twitter.com/#!/edvakf/status/133427444341411840

とちょっと考え方を変えた。

StructType, ArrayType がおもしろいと思った理由はもうひとつあって、↓これの上半分を省略して Triangle = function(x){return Object.create({},x)} とするとこのコードは ES5 の範囲に収まることになる。(このままだと ES5 では予約後でエラーが出るけど)

const Point2D = new StructType({ x: uint32, y: uint32 });
const Color = new StructType({ r: uint8, g: uint8, b: uint8 });
const Pixel = new StructType({ point: Point2D, color: Color });
 
const Triangle = new ArrayType(Pixel, 3);
 
let t = new Triangle([{ point: { x:  0, y: 0 }, color: { r: 255, g: 255, b: 255 } },
                      { point: { x:  5, y: 5 }, color: { r: 128, g: 0,   b: 0   } },
                      { point: { x: 10, y: 0 }, color: { r: 0,   g: 0,   b: 128 } }]);
...
http://wiki.ecmascript.org/doku.php?id=harmony:binary_data

use strict とか object descriptor とか、WeakMap や Proxy や上に挙げた StructType, ArrayType は、JIT コンパイラーにヒントを与えて最大限の最適化を引き出すためのもの。手書きの JS でも十分に恩恵を得られるぐらい少しの手間で。もちろん多言語から JS にコンパイルするにも役立つけど。

JS の進化は互換性には最大限配慮して、パフォーマンスを向上させるものであってほしい。Ruby や Python みたいに簡単に書けるようにとか、そういう方向ではないと思ってる。

Web Refrection の人(Nokia の Andrea Giammarchi さん)も同じ事言ってた。


Transpiler

繰り返しになるけど、JS は他の言語をその上で走らせるのに十分な速さになっていくだろうし、むしろそっちのほうが速くなる場合もあると思う。Ruby みたいなシンタックスが欲しければ Ruby to JS transpiler を書けばいいだけ。

Ashkenas さんと Eich さんが強調してるのは、自分で JS.next を作りなさいということ。ぼくがかんがえたさいきょうのじゃばすくりぷとを実現するには JS が変化するのを待っててはだめ。まず実装してみて、世に広めて、デファクトスタンダードになったら JS.next に取り入れられるから。

僕や Web Refrection の人はシンタックスの変更には否定的だけど、もしそうなったとしたら、数年かかるであろう移行期間は JS.next to JS transpiler が使われることになるんだろう。たぶんその transpiler は CoffeeScript みたいに可読性重視のものと Emscripten みたいにパフォーマンス重視のものと両方(というか何種類も)作られるだろう。

もしかするとその間に人々は transpiler の意義に気づいて、生の JS を書く人は少なくなるかもしれない。

それでもいいんですよ。だって JS は21世紀の C 言語なんだから。

2011-11-03

bullet

14:20

今作ってる WebGL のプログラムbullet が使いたいので調べてる。


bullet.js

C++ の物理エンジンである bullet が Java に移植されたものが jbullet で、jbullet が JavaScript に移植されたものが bullet.js。

bullet.js は pl4n3 という人がブログに載せたデモページの中にあって、github 等の公式レポジトリは無い。他人が転載したやつはあるけどメンテされてない。

jbullet のバージョンは本家 bullet より遅れてて、特に MMD で使ってる btGeneric6DofSpringConstraint という関数が無いらしい。

また bullet.js は、一緒に移植されてる行列演算用のライブラリ Vecmath に依存してて、glMatrix などの好きな行列演算ライブラリをそのまま使うことはできない。数字だけコピーすれば大丈夫なのであんまり気にする必要はないかも?

f:id:edvakf:20111103140336p:image

↑WebGL じゃなくて 2D Canvas でやってる。fps はかなり良い。


ammo.js

そういうことで、最新の bullet を Emscripten で JS にした ammo.js というのが作られた。

bullet.js と比べてパフォーマンスがどうなのか気になるところ。↑のサンプルコードのデモだと fps がかなり悪いけど、↓だとそこまで酷くない。

この人の感想によると、メモリリークが激しいとかなんとか。うちではそんなことは無い気がするんだけど。ammo.js の readme の説明にある destroy はちゃんと呼んでるんだろうか。あと C++ でコンストラクターが複数あると JS からは最初の一つしか使えないとか。

f:id:edvakf:20111103141709p:image


NaCl

C++ 版の bullet が変更なしで NaCl でビルドできるらしい。

Chrome Web Store 限定になるのであまり魅力を感じないが、もし今作ってるやつの物理演算バックエンドを切り替えられるようにするとしたら、これを使うのもありかもしれない。

デモはダウンロードして走らせてみた。bullet の部分だけじゃなくてアプリケーションも全部 NaCl で作られてる。

物理演算だけ NaCl にやらせることもできるんだろうか(たぶんできる)。その場合は postMessage を使った非同期通信になってしまうはず。まあどちみち物理演算をやるなら Workers の中でやらないとスピードがキツいと思うので、postMessage の向こうをブラックボックスにしてしまえばあまり特別な手間をかけなくても使えるかも。

f:id:edvakf:20111103141815p:image


その他

そもそも bullet じゃないといけないのかどうか知らない。JigLib というのが JS にポートされてて良さげな感じではある。

f:id:edvakf:20111103144338p:image

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

2011-10-25

uupaa さんの msgpack.js がパクられてた件

10:29

BadAss JavaScript にこんなのがあって、

I also released CoffeePack, an implementation of the MessagePack serialization format in pure CoffeeScript.

Badass JS is back with a new look! Here’s a little... | Badass JavaScript

おお!?と思ったので見てみたら、

                    # float
                    else
                        # TODO: encode single precision if possible
                        sign = val < 0
                        val *= -1 if sign
                
                        # add offset 1023 to ensure positive
                        exp = ((Math.log(val) / Math.LN2) + 1023) | 0
                
                        # shift 52 - (exp - 1023) bits to make integer part exactly 53 bits,
                        # then throw away trash less than decimal point
                        frac = val * Math.pow(2, 52 + 1023 - exp)
                
                        low = frac & 0xffffffff
                        exp |= 0x800 if sign
                        high = ((frac / 0x100000000) & 0xfffff) | (exp << 20)
                
                        bytes.push 0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff,
                                         (high >>  8) & 0xff,  high        & 0xff,
                                         (low  >> 24) & 0xff, (low  >> 16) & 0xff,
                                         (low  >>  8) & 0xff,  low         & 0xff

なんか見覚えが…

前に書いた IEEE754 のコードに酷似してる。

というか uupaa さんの msgpack.js ↓とコメント含めて同じなんですけど><

            } else { // double
                // THX!! @edvakf
                // http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731
                sign = mix < 0;
                sign && (mix *= -1);

                // add offset 1023 to ensure positive
                // 0.6931471805599453 = Math.LN2;
                exp  = ((Math.log(mix) / 0.6931471805599453) + 1023) | 0;

                // shift 52 - (exp - 1023) bits to make integer part exactly 53 bits,
                // then throw away trash less than decimal point
                frac = mix * Math.pow(2, 52 + 1023 - exp);

                //  S+-Exp(11)--++-----------------Fraction(52bits)-----------------------+
                //  ||          ||                                                        |
                //  v+----------++--------------------------------------------------------+
                //  00000000|00000000|00000000|00000000|00000000|00000000|00000000|00000000
                //  6      5    55  4        4        3        2        1        8        0
                //  3      6    21  8        0        2        4        6
                //
                //  +----------high(32bits)-----------+ +----------low(32bits)------------+
                //  |                                 | |                                 |
                //  +---------------------------------+ +---------------------------------+
                //  3      2    21  1        8        0
                //  1      4    09  6
                low  = frac & 0xffffffff;
                sign && (exp |= 0x800);
                high = ((frac / 0x100000000) & 0xfffff) | (exp << 20);

                rv.push(0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff,
                              (high >>  8) & 0xff,  high        & 0xff,
                              (low  >> 24) & 0xff, (low  >> 16) & 0xff,
                              (low  >>  8) & 0xff,  low         & 0xff);
            }
            break;

issue 作ってみた。


修正された。

本当なら Copyright のところに連名にするべきだろうけど、(The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.)自分としてはこれで十分なのでもういい。


本当なら自分のコードはパブリックドメインにしてもいいと思ってるんだけど、今回は自分だけのコードじゃなかったのでかっとなった。良いコードはパクられる、と思えば気にならない。

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

2011-10-10

Firefox の WebGL がなんか変

19:45

  var size = 200
  var canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  canvas.style.border = 'solid black 1px';

こんな感じで作った canvas 要素なんだけど、Firefox では WebGL で何かを描画しても 200px 埋まってくれない。Chrome だとこんなことはないのに。

f:id:edvakf:20111010192639p:image

いろんなピクセルで実験。

250

f:id:edvakf:20111010193025p:image

右下がちょっと開いてるのがわかる

256

f:id:edvakf:20111010193121p:image

ぴったり。

257

f:id:edvakf:20111010193217p:image

どうやら「width/height の数字以上で最小の 2^n 型の整数」の正方形に描画して、それを無理やり縮小してるみたい。

300

f:id:edvakf:20111010193418p:image

500

f:id:edvakf:20111010193531p:image

ぴったりじゃないのでぼやけてる。

512

f:id:edvakf:20111010193627p:image

ぴったり&くっきり。

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

2011-10-08

Google Closure Library の一部分だけを単一ファイルにしたい

03:13

そういうときにどうしたらいいかを調べるだけでも一苦労。情報が少なすぎるのが closure の難点。

必要なもの: python, svn

svn は使わないでもいいけど、ちまちま必要なファイルをダウンロードするよりは全部一気にやってしまったほうがいいと思う。

まず、svn で closure をダウンロードしてくる。けっこう時間がかかる。

svn checkout http://closure-library.googlecode.com/svn/trunk/ closure-library

本当に必要なのは

  • closure-library/
    • closure/
      • bin/
        • build/
          • この中身

と、

  • closure-library/
    • closure/
      • goog/
        • base.js
        • deps.js
        • その他自分が使いたいやつ

だけのはず。(たぶん)

次に、まっさらなディレクトリを作る。ClosureBuilder (後述)でビルドするときに、ディレクトリの中の全ファイル(?)を見るみたいなので、余計なのが入ってると変なエラーが出たりするから。

この中に、こういうファイルを作って、名前は tmp.js とでもする。

goog.provide('closure-vec');

goog.require('goog.vec.Vec3');
goog.require('goog.vec.Mat4');

Vec3 = goog.vec.Vec3;
Mat4 = goog.vec.Mat4;

provide のところは何でもいい。最後の2行は、goog.vec とかいちいち書きたくなかったので付けた。

ここで、ClosureBuilder を使う。自分は $HOME/local/closure-library にダウンロードしたけど、その部分は置き換えてください。

python ~/local/closure-library/closure/bin/build/closurebuilder.py --root=$HOME/local/closure-library --root=. -n closure-vec -o script --output_file=closure-vec.js

--root が2つあるのは、一つは closure-library のトップを指定して、もう一つは tmp.js ファイルがあるディレクトリを指定。

-n は、さっき goog.provide した名前。ClosureBuilder が勝手にディレクトリ内の全ファイルをスキャンして goog.provide を見つけてくれる。

-o script はタイプを書くらしい。よくわからないけど script でいいらしい。

--output_file は出来上がるファイル名。

そうすると、このディレクトリ内に tmp.js とは別に closure-vec.js ができてて、中身は非圧縮の base.js とその他指定したライブラリが連結され、最後に自分が書いた tmp.js の中身が入ってる状態になってる。

tmp.js は捨てて closure-vec.js を HTML から読み込めばいい。

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