FOSS4G Advent Calendar 2013参加の記事です。
タイトル書いて、いやよく考えるとブラウザクライアントサイドのFOSS4GってOpenLayersとかいろいろあるじゃんと思ったけど別にそのままでいいや。
emscriptenって?
こちらで開発されている、c、c++で開発されているコードをJavaScript環境で動く形にコンパイルする技術です。
開発者の方じきじきの紹介プレゼンがあるので、それを見てもらうともろもろよく判ります。 その1、その2
emscriptenとの出会い
最初の出会いはFOSS4G界隈でタイル地図技術、特にsqliteベースのmbtiles仕様等が話題になった頃です。
かねてより私は、地図はmp3とかflvみたいに、ブラウザで単独ファイルとしてレンダリングできるようなコロッとした仕様が必要、というのが持論でした。
そこでsqliteをプラウザサイドだけで読める方法を見つければ、mbtilesをそんな仕様の叩き台にできるなと思って探し始め、見つけたのが、emscriptenでsqliteそのものをコンパイルしたsql.jsだったのです。
これでmbtilesをブラウザでパースできるなと思い調査し始め、mbtiles.jsというJavaScriptライブラリを作りました。
githubはこちら(最新版のemscriptenでコンパイル通るように直してあります)、サンプル動作はこちら(こちらは最近手直しする前の古いライブラリ)。
その次に作ったのが、マルチプラットフォームの地図/絵地図座標リアルタイム変換エンジンの叩きとしての、ThinPlateSpline変換c++ライブラリのJavaScriptコンパイルでした。
サンプルはこちら(Bing地図と古地図、どちらかの上でマウスクリックしてみてください。これも、githubは最新、サンプルは少し古いです)。
##ギークのおもちゃだと思っていたけれど…実は次世代のWeb技術だった
その位使ってみたのですが、参考にしたsql.jsも実はストレージデータは数値や文字列だけ、と決め打ちした実装になってて、タイル画像のようなBLOBを扱えるようにするには下層レイヤーまで手をつっこまないといけなくて、結構大変でした。
おかげで、それなりにemscriptenの考え方も判るようにはなったのですが…。
でも、適用サンプルともいえるsql.jsがそんな感じでちょっと手抜いてるなーという感じだったので、よくあるJavaScriptでLinux実装してみましたー、みたいなスーパーギークが遊びでやってるプロジェクト感がして、半年以上さらに突っ込んで触る事はありませんでした。
再会したのはこちらの記事でした。
次世代のWebブラウザ技術としてMozillaのasm.jsとかGoogleのPNaClとかがあるよと、ふんふんと読んでいたら、asm.jsという方を支える技術としてemscriptenがあるとの記述が。
びっくりしました、あの技術そんな凄かったのかー、と。
また、自分でベンチマーク取ったりもしてみたのですが、proj.4をemscriptenでコンパイルしたJavaScriptライブラリをJavaScriptネイティブのProj4JSと比較してみたところ、10000回の試行をJavaScriptループで回してやるとemscripten:Proj4js = 30ms:700msくらいの実行速度差。
10000回分の試行データをTypeArrayにぶち込んで一気に渡してやると、何とemscripten:Proj4js = 5ms:700msくらいの実行速度差が出ました。
これ、cネイティブで実行しても1ms〜2msくらいみたいなので、むちゃくちゃすごい!
FOSS4Gのc、c++資産がブラウザサイトに持って来れちゃうかも
c、c++のFOSS4Gソフトウェア資産というと、proj.4をはじめとして、GEOS、GDAL/OGR、spatialite、QGIS、等等いろいろありますが、全部ブラウザサイドでは走らせられないという前提で、最近のWeb上GISノウハウは設計されている感じがあります。
地図タイル工法等もその一つだと思います。
が、全部ブラウザサイドに部品として持ってこれるとしたら、話は全部ひっくり返るのではないでしょうか?
QGISなんかも、部品の一つであるQtは既にemscriptenでブラウザサイドに持ってこれてますので(クソ重いですが)、遠い将来にはブラウザ上でQGISを動かすのも夢ではないでしょうし、また近い将来でも、いろんな部品がサーバサイドからブラウザサイドに流れ込めば、Webツールの作り方も変わってくるでしょう。
もちろんそのためにはまだまだノウハウの蓄積が必要で、上で紹介したemscriptenコンパイル版のproj.4も、私は自分ではコンパイルできず他の人がコンパイルしたものを借りてきたものですが、みんなで必要性と興味を共有してノウハウを貯めていけば、できる事は増えていくと思います。
実際に触ってわかったハマりどころ
とりあえず触り始めるかもしれない人のために、先に取り組んで判らなくて悩んだりしたところをいくつか。
cやc++を、人間がイメージするJavaScript構文に直してくれるものではない
最初、これが判ってなくてハマりました。
例えば、こんなc++関数
int loop(int n) {
int i;
for (i=0;i<n;i++) {
}
return i;
}
が、
function loop(n) {
var i;
for (i=0;i<n;i++) {
}
return i;
}
こうなるわけじゃなくて、
function __Z4loopi($n){
var label=0;
label = 1;
while(1)switch(label){
case 1:
var $1;
var $i;
$1=$n;
$i=0;
$i=0;
label=2;break;
case 2:
var $3=$i;
var $4=$1;
var $5=($3|0)<($4|0);
if($5){label=3;break;}else{label=5;break;}
case 3:
label=4;break;
case 4:
var $8=$i;
var $9=((($8)+(1))|0);
$i=$9;
label=2;break;
case 5:
var $11=$i;
return $11;
default: assert(0, "bad label: " + label);
}
}
こうなります。
ようするに、アセンブラっぽいロジックと、仮想マシン的なものを実現してるわけですね。
メモリ空間としてはTypedArrayを使って、最初にメモリ空間を定義して、スタックポインタ動かして、メモリをalloc、deallocして。
ThinPlateSplineJSで、c++クラスをインスタンス化した際も、こんな感じのコードを書いて、手動でメモリの確保、解放を行いました。
//メモリ確保
var pointer = Runtime.stackAlloc(104);
//c++コンストラクタ呼び出し、
Module['ccall']('_ZN17VizGeorefSpline2DC2Ei', 'void', ['number', 'number'], [pointer, 2]);
//デストラクタ呼び出し
Module['ccall']('_ZN17VizGeorefSpline2DD2Ev', 'void', ['number'], [pointer]);
//メモリ解放
Module['ccall']('_ZdlPv', 'void', ['number'], [pointer]);
この辺、今はembindとかいう機構が準備されて、普通にJavaScriptのクラスっぽく呼び出せたりできるようになってるみたいですが、私が使ってた頃はこんな感じで呼び出す必要がありました。
最適化するとガシガシコードが削られます
先の例のように、基本、アセンブラのような形で変換されたc/c++のコードに、Module.ccallというAPI経由でアクセスする形ですが、コンパイルに最適化オプション -O2 等をつけると、mainループからの実行で使われていない関数はガシガシ省略されます。
また、使われている関数でも、関数名の短縮化等が行われるので、実行プログラムではなくライブラリとして使いたい場合等、呼ばれていない関数もコンパイルして関数名も維持したい場合等は、-s EXPORTED_FUNCTIONS="[関数名リスト]" オプションを使って、残したい関数名を指定しましょう。
この時指定する関数名は、メソッド名そのままではなく、c++ならばname manglingされたようなシンボルなので、その点の注意が必要です。
他のJavaScriptから呼び易いようなインタフェースは自分で用意する必要がある
c/c++をコンパイルしてくれるのはいいですけど、Module.ccallだのname manglingされたシンボルだの、他のJavaScriptからはいちいち呼べないですよね…。
それの解決のために、コンパイル時に生成したJavaScriptコードの前後に、付加するファイルを指定するオプションがあります。
emcc source.c --pre-js pre.js --post-js post.js -o result.js
のように、--pre-js、--post-jsで前後に付加する内容を指定できますので、そこに書いたコードでややこしい呼び出しを隠蔽化しましょう。
最新版の吐くコードはClosure-Compiler等の短縮化が効かないため、独自の圧縮法を使う必要がある
以前は生成されたJavaScriptをClosure-Compiler等を通して、いわゆるXXXX.min.jsを生成できたのですが、最新版の吐くコードはうまく通らなくなっています。
その代わりに独自の圧縮法が用意されたようですが、これが面白くて、なんとzlib自体をemscriptenでコンパイルして、その展開コードを付加してメインコードはzip圧縮して送って、ブラウザ上で展開するというやり方のよう。
まだ試したことはありませんが、流石の発想ですw
他ライブラリのリンク、configure、make等も準備されつつある
徐々にいろいろな環境が整ってきつつあるようで、私がアクティブに使っていた頃にはなかった、外部ライブラリのリンク機能や、emconfigure、emmake等のツールも準備されて、proj.4のようにプロジェクト全体をコンパイルできるような環境も整いつつあるようです。
最後に:GIS業界は頑固オヤジであって欲しい
以上、簡単に、FOSS4Gの諸資産をブラウザサイドに持って来れるかもしれないemscriptenについて紹介させていただきましたが、最近のWeb界隈に近いGIS業界を見ていて思う事を…。
私がGIS、というかジオメディア界隈に顔を出した頃、GIS業界というのはすごく頑固一徹なこだわり職人的な業界だったように思います。
回転楕円体じゃない球面メルカトル?不正確だろJK、ぽっと出のプッシュピンプロバイダーが云々、位置情報扱うなら空間DB必須だろ、地図はトポロジが、位相がetc…
そんな頑固な人々が持ってる超優秀な黒魔法に憧れつつ、その一端を知って、マージナルな領域でうまく情報をやり取りすることで、私みたいな外様でもそれなりにやってこれたのですが、
最近、ちょっと頑固オヤジが丸くなりすぎな感じがします。
Web技術という大津波の元に、それに合わせて必要な部分を変えていくのはいいのですが、譲る必要のないところまで譲り過ぎてるような気もしないでもないのです。
Web…と言っても、Tim Barners-LeeがどうのLinkedDataがどうのというようなそれと、GoogleがどうのTwitterがSNSがどうのというようなそれによっても違うとは思いますが…に寄ろうとしても、例えばこの記事、一番ベンチマークが速いのはGIS技術バリバリの空間DBですが、MyISAMやPostgresは運用のメンテナンス性が、とかなんとか実際のWeb運用では確実に無視される。
それならば次点として、まさにWeb向けの地図タイル工法、GIS的にも意味があって速度も速い、KVSでも使えるquadkeyを紹介しても、普通のWeb業界では完全に黙殺されて、何の意味も知性もないGeoHashが使われ続けるわけです(私が影響力を及ぼせる、某1000万人会員のWebサービスでは空間検索はquadkey/GeoHex化していますが)。
その他の技術にしてもそういう側面あると思いますが、それだったら必要以上にすり寄らずに、独自黒魔術を追求し続けるというのもアリではないでしょうか?
それができる環境も整いつつあるのならばなおさら。
で、そうやってGIS業界の方々が黒魔術に引きこもってくれれば、また私なぞがマージナルな領域で食えるようになると(それが本音かよ!)。
というのはまあ冗談ですが、一昔前、使い方もよく判らないGISツールをコンパイルしようと四苦八苦して、なんとかコンパイルできた、やった、これでまた一つできる事が増えるぞ!と面白かったのを覚えていますが、
emscriptenでブラウザサイドにもってこれるソリューションが一つまた一つと増えて、あのワクワク感が再来すればいいなあ、と思っています。