HSP JIT

何年か前に放置してたHSPJITを再開。

実行時情報による最適化無し(--no-opt)はhsp3cnv+コンパイル相当の内容で、バイトコードのデコードとディスパッチ、オペランドスタックの操作などを最適化出来て数割の高速化。

プログラム実行中に変数型が変わらないと仮定(--skip-type-check)し、配列のインデックスチェックを省く(--no-range-check)とaobenchやレイトレのようなひたすら計算をするプログラムでは10倍の高速化出来た。

課題

  • hsp3cnvの実装を踏襲していて制御構文命令や関数呼び出しをまたいだ最適化が出来ない
  • 過剰に強い仮定をしていてhsp3.4付属のサンプルのうちかなり多くが動かない
  • 命令をHSP3TYPEINFO経由で呼び出すと引数のStackPush()/StackPop()が重い
  • JIT/LLVMの習作も兼ねていてとっちらかってる

http://peppermint.jp/products/hsp/hsp3ll-20150502.zip

今年の振り返り

囲碁AI

コンピュータ囲碁モンテカルロ法の理論と実践読みつつ、OCamlモンテカルロ木探索なの書いた。
マルチプロセッサで検証したくてAWS使い始めたり。棋譜からのパターン学習のあたりで力尽きてGNU Goに勝ち越せてない。
OCamlは書きやすいっていう利点はあるけど、スレッド使えない、C++Java比で遅いってあたりであんまり向いてないタスクかもしれない。
ispcでなんとかなるかと思ったけど一番高速化したいあたりとC/C++だとだるいあたりがかぶってていまいち。

TypeScript

会社でSPA作ったりDefinitelyTypedやTypEcsにパッチ送ったり。
家でもHSP関係の小物に使ったり、DTにパッチ送ったり。
js_of_ocaml的嬉しさはあんまりないけど、ツラミもあんまりないので、しばらくはWeb系開発はTSでいいやと思ってる。

HSP

AltJS勉強会の発表駆動開発でEmscriptenでウェブブラウザ対応を書いて、先日公開されたHSP3.4にマージされた。
ここ数年コンテストどうしようかと思うけど作るのに半日以上かかるものをHSPで書く気力があんまりなくて毎回見送ってる。

ここ数日HGIMG4をウェブ版に入れられないか実験中。

OCaml

前述の囲碁書いたり、ocamlcのasm.jsバックエンドの実験したり。
asm.jsターゲットだとjs_of_ocamlが回避した面倒さをもろに被る割にはアウトパフォームできるかは対象次第の様子。リンカなどの周辺領域はEmscriptenの資産使えるだろうという目論見が外れてGC実装でつまずいてる。

Jetson TK1

セットアップしてサンプル動かしたあたりで単純に絶対性能ならその辺のPC使うほうが楽っていうのがあって放置状態。

OculusRift

家ではRiftSketchのようなことをXサーバー&WMのレイヤーでやろうとして、VR酔いで放置状態。
会社で同僚やお客さんにデモして反応はいいので来年は何か作るかもしれないけど、これも私のVR酔いをなんとかしないと辛い。

仕事

去年までに続いて基本的にJavaGUIアプリ作成、時々Scalaでサーバーやら、TypeScriptやOCamlでウェブアプリやら。

TypEcs

今後仕事でTypeScript多用することになりそうだったので、Eclipse向けのTypeScriptプラグインの一つのTypEcsにちまちまpull request投げてた。

手元環境で特に変更無く動いたのとデバッガ内蔵していたり多機能ということもあってTypEcsをメインに見ていたけれど、ソース眺めてるとEclipseプラグイン作りはeclipse-typescriptの人々のほうが慣れてそうな印象。統合されればいいのに……

HSP3Dish.js

OpenHSPのhsp3dishをemscripten対応した。

http://peppermint.jp/products/hsp/hsp3dish.html

FirefoxChromeIEとも最新バージョンでは実行速度問題なさげ。
IE11だとWebGL対応状況の関係でblock3はボール見えないくらい。

hspcmp.dll相当の機能も動いてるんで、全部オンラインでプログラミングってのも出来そう。
http://peppermint.jp/products/hsp/hspcmp2.html

公式のサンプルは動いたんで、実環境な実行テスト用に誰かdishアプリ下さい。

js_of_ocamlとenchant.js

TypeScriptとenchant.jsの相性悪いという話があったけどjs_of_ocamlも辛い。
enchant.Class.createをうまく扱う方法が思いつかない。

継承使わずに分厚いラッパー用意するほうがいいのかも。

open Js

module Enchant = struct
  let enchant =
    Unsafe.variable "enchant"

  let init modules =
    let ms = jsnew array_empty() in
    modules
    |> Array.iter (fun m ->
      ignore @@ ms##push(string m));
    ignore @@ Unsafe.fun_call (Unsafe.variable "enchant") [|Unsafe.inject ms|];
    ()

  let new_game w h =
    let game = Unsafe.variable "Game" in
    jsnew game (w, h)

  let game_assets game key =
    let g = Obj.magic game in
    Unsafe.get (g##assets) (string key)
end

module E = Enchant

let _ =
  E.init [||];
  Dom_html.window##onload <- Dom_html.handler (fun _ ->
    let game = E.new_game 320 320 in
    game##enemy_speed_ <- 1;
    game##preload(string "chara1.png", string "map0.png");

    let bear_class =
      E.enchant##_Class##create
        (E.enchant##_Sprite,
         Unsafe.obj
           [|
             "initialize", Unsafe.inject @@ wrap_meth_callback (fun this x y ->
               E.enchant##_Sprite##call(this, 32, 32);
               this##x <- x;
               this##y <- y;
               this##image <- E.game_assets game "chara1.png";
               this##frame <- 5;
               game##rootScene##addChild(this)
             )
           |]) in

    let enemy_class =
      E.enchant##_Class##create
        (bear_class,
         Unsafe.obj
           [|
             "initialize", Unsafe.inject @@ wrap_meth_callback (fun this x y ->
               bear_class##call(this, x, y);
               this##addEventListener
                 (string "enterframe",
                  wrap_meth_callback (fun this () ->
                    this##x <- this##x + game##enemy_speed_;
                    let frame = (this##age /. 5.0 |> floor |> int_of_float) mod 4 in
                    this##frame <- [|0; 1; 0; 2 |].(frame) + 5)))
           |]) in

    game##onload <- wrap_callback (fun() ->
      game##rootScene##addEventListener
        (string "enterframe",
         wrap_meth_callback (fun this () ->
           if this##age mod 20 = 0 then
             let _ = jsnew enemy_class (0, Random.int 320) in
             ()
         )));

    game##start ();
    _false
  )

OCamlからIntel SPMD Program Compilerを使う

せっかく最新のCPUではコア複数あるんだからOCamlからも活用したい。

ispcではGPGPUのようなイメージで独自言語のコードを専用コンパイラコンパイルしたものを普通のC/C++のコードから呼び出して使う。だったらocamlからもctypes.foreign経由で素直に使えるんじゃなかろうかということで実験。

$ ispc -O2 --arch=x86-64 --target=sse2,sse4-x2,avx sort.ispc -o objs/sort_ispc.o -h objs/sort_ispc.h
let s =
  let open Ctypes in
  foreign "sort_ispc"
  (int32_t @-> ptr uint32_t @-> ptr uint32_t @-> int32_t @-> returning void)

let sort_ispc' n code order ntasks =
  let n' = Int32.of_int n in
  let code' = C.Array.start code in
  let order' = C.Array.start order in
  let ntasks' = Int32.of_int ntasks in
  s n' code' order' ntasks'
$ time ./sort 10000000
[sort ispc]:    [110528.213] million cycles
[sort ispc + tasks]:    [73224.223] million cycles
[sort serial]:          [847924.817] million cycles
                                (7.67x speedup from ISPC, 11.58x speedup from ISPC + tasks)
./sort 10000000  579.76s user 5.71s system 112% cpu 8:41.84 total

10000000要素のソートがArray.sort比で1スレッドは7.67x、4スレッドは11.58x早い。オーバーヘッドがあるらしく1万要素程度だと逆に遅いので使いどころを選びそうではあるが一応使えそう。

https://github.com/zakki/ocaml-ispc-examples