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 言語なんだから。

 |