最近のブログ

RDF(RSS 1.0) URL RSS URL ATOM URL

2012年02月02日

吉里吉里Java :: レンダリング済みフォント

レンダリング済みフォントのサポートをどうするか悩んでいたけど対応した。
Java では縦書きフォントが読めない。
Android も調べた範囲では読めない。
後、フォントを共通化する為にもレンダリング済みフォントは有用と言うことで対応した。

FreeType の Java 版があるか探したが見付からず。
自力で FreeType を Java に移植することも考えたが、かなり労力がかかりそう。
FreeType があれば、共通化や縦書きも出来るようになると思うが、対応は大変。
と言うことで、自前でのフォント関係の処理をすることは諦めた。


少メモリ環境下では、ディスク容量やメモリ容量節約のため使用する文字のフォントデータのみ出力したいが、オリジナルのレンダリング済みフォント作成ツールでは指定した文字のフォントデータのみ出力する機能はない。
ツールのソースを読んでもそれっぽい機能はない。
機能追加するのはそれほど難しくなさそう。
作るかどうか悩んでいたら、既にあることを教えてもらった。

吉里吉里レンダリング済みフォント作成ツール改

投稿者 Takenori : 23:35 | コメント (0) | トラックバック (0)

2012年01月28日

吉里吉里Java :: Layer.affine 系

吉里吉里2の Layer.affine 系のメソッドは、matrix 指定と頂点指定のどちらかを選べる。
吉里吉里2では、matrix で指定された場合、頂点に変換して、頂点を使って描画する。

吉里吉里Java の場合は、常に matrix を指定して描画する。
そのため頂点指定の場合は、頂点から matrix を割り出す必要がある。
matrix から頂点を求めるのは簡単 (普通の行列演算) だけど、逆は手間がかかる。
最初は幾何学的に求められるかと思ったが、いろいろ検討するも難しい。
と言うことで方程式を解くことに。
吉里吉里2では3つの頂点が与えられるので、2つの連立三元一次方程式を作ることができる。
これは matrix から頂点を求める式そのまま。

連立三元一次方程式1
x0 = (-0.5)*a + (-0.5)*c + tx
x1 = (width-0.5)*a + (-0.5)*c + tx
x2 = (-0.5)*a + (height-0.5)*c + tx

連立三元一次方程式2
y0 = (-0.5)*b + (-0.5)*d + ty
y1 = (width-0.5)*b + (-0.5)*d + ty
y2 = (-0.5)*b + (height-0.5)*d + ty

このうち a, b, c, d, tx, ty が求める値。
それ以外は事前にわかっている。
連立方程式は、ガウス=ザイデル法やガウスの消去法等いくつか数値演算で求める方法がある。
効率を考えてガウス=ザイデル法を用いて上式を解くようにしたら、解が収束せずに求められず、ガウスの消去法を用いることにした。
期待通り matrix が求められている。

ただ、オリジナルとは異なり頂点を与える方が方程式を求める分効率が悪い。
直後の描画処理に比べたらそれほどでもないかも知れないが。

投稿者 Takenori : 17:28 | コメント (0) | トラックバック (0)

2012年01月24日

吉里吉里Java :: サスペンド / リジューム

Android での起動の高速化のためにあれこれ考えているが、起動した直後の状態を直接構築出来れば、それが一番速い。
startup.tjs を実行した直後の状態が起動直後の状態なので、その時の状態を保存して、その状態を復元することが出来れば理屈上は最も速く起動が完了するはず。
ただ、環境によって起動状態を変更する必要が生じた時に対応出来ない。
依存しそうなものは、画面解像度とSDのパス辺りかな。
依存部分だけ保存しない作りにすればいいが、後々維持管理が大変になる可能性が高い。
startup.tjs 実行直後の状態を保存して~と言うのは避けた方が良さそう。

いつでも状態を保存して復元できると言うのは、Android だとあるとかなり助かる機能なのは間違いない。
他のアプリが立ち上がってバックグラウンドに行って、気付いたら死んでることがあるので、オートセーブは必要になるだろうし、スマートフォンの用法を考えるといつでも中断/再開が出来るとありがたい。

理屈上はシリアライズに対応すればサスペンド / リジュームに対応出来る。
シリアライズの問題点として、オブジェクトを保持している時はいいとして、参照しているだけの場合どのようにしてその状態を保存し復元するのかと言うものがあり、これは2パスで保存するしかないと思っていた。
少し調べて見ると、これは 時空を越えるオブジェクト の一番下の方法によって解決出来ることがわかった。
後、問題となるのはレイヤー画像の保存。
読み込んだままの画像はファイル名を覚えておけばいいが、変更が加えられている場合は画像そのものを保存しなければならない。
メッセージレイヤーは確実に画像を保存することになると思われる。
他には、TJS2 で記述された各種オブジェクトのバイトコードの保存と読み込み、全クラスでのシリアライズ処理のサポート。
保存と復元に時間がかかりすぎるとあっても無意味。

サスペンド / リジューム出来るメリットは大きいが、その分実装とテストも大変。
対応するかどうか悩むところ。

投稿者 Takenori : 17:39 | コメント (0) | トラックバック (0)

2012年01月13日

吉里吉里Java :: バイトコードファイル読み込み

現在以下仕様で実装している。
Script.execStorage Script.evalStorage で指定されたファイルの拡張子が tjb ならバイナリバイトコードファイルとみなして読み込む。
拡張子 tjs の時は、同名で拡張子が tjb のファイルがあるかどうか見て、あったら tjb ファイルをバイナリバイトコードファイルとして読み込む。
上記、条件に合わない時は tjs ファイルをスクリプトとして読み込む。

この仕様について、以下のような提言を受けた。
----
いまどきの実装なら拡張子依存ではなく、マジックコード読んで処理するほうが良い。
a. 内部的なロード処理ではマジックコードでどちらでも読めるようにする
b. ファイル名のマッピングを差し替えれる機構をプラグ的に準備(拡張子を差し替えて優先ロード、とか) はそれぞれ別にする。
勝手に指定してないファイルが読まれる可能性がある仕様は個人的には好ましくない。
----

この部分の仕様は開発をどのように進めるかが仕様決定の主因であり、それを想定して仕様を決めた。
拡張子依存ではなく、マジックコードで~と言うのは確かに一理ある。
一応、バイトコードかどうかの判定は、埋め込まれた FourCC 等によって判別はしているが、一致しなかった場合にスクリプトとみなしてコンパイルを試みる等の動作はしていない。

スクリプトをそのままコンパイルして、コンパイルしたバイナリをデータフォルダに突っ込めば、勝手にそっち読んで動いてくれる。
バイナリとスクリプトを見分けやすい。
バイナリファイル消せば、スクリプト読んで動いてくれる。
これらの動作を元に仕様を決めた。

提案のあった内容は、どのような使い方を想定したものなのだろうか?
拡張子ではなく、マジックコードで判定するのはいいとして、引き渡すファイルの拡張子は tjs にするんだろうか?
だとしたら、スクリプトの tjs ファイルと、バイトコードになった tjs ファイルが混在して混乱の元だと思うけど、そこは気にしないのだろうか?
もしくは、引き渡すファイルの拡張子を元々 tjb 等として、バイトコード前提のスクリプトを別に準備するのだろうか?
バイトコード前提のスクリプトを別に準備するとなると、常にコンパイルしなければならなくなるが……
毎回コンパイルを避けるとなると、TJS で自前で tjb ファイルがあったらそっちを読む等の判定処理を書いて、それを使う前提なのだろうか?
TJS で自前で書とある程度スクリプトに修正を入れることになるが、上述した中ではこの使い方が一番現実的か。

拡張子ではなく中身で判別するべき。
指定していないファイルが読まれるのは好ましくない。
と言う二点によって、提案されたもので実際の用法を考慮に入れたものではない可能性もある。


再度仕様を考える。
1. 指定されたファイルの中身でファイルの種類を判別し、スクリプトかバイトコードとして読み込む。
2. バイトコードでない場合は、tjb ファイルを探すが、探すかどうかはオプションによって切り替え可能とする。
このような形が妥当か?
ただ、この場合 TJS2 以外のスクリプトを許容するようになった時に破綻するが、その時は拡張子で判定を再び復活させるか、別のメソッドにすることになる。
まあ、そうなることはなかなかなさそうだけど。
後、推奨する使用法は最初に書いた拡張子を使ってくださいと言うことになるかな。

----
追記
開発時はスクリプト、リリース時にバイナリ化すると言う運用なので、ファイル名は同名で問題ないと言うことのようだ。
運用上混在しない形だったのか。
ただ、Android だとコンパイル時間がある程度かかるので、事前コンパイルしておいた方が良いと言うことについては、フォルダを分けで make 等使い毎回コンパイルするようにして回避すると言う案のよう。

投稿者 Takenori : 18:15 | コメント (0) | トラックバック (0)

2012年01月09日

Android :: AssetManager.list が遅い

Xperia arc のみかもしれないが、AssetManager.list が遅い。
100msec程度もこの呼び出しにかかっている。
呼び出す度に apk ( zip ) ファイルアクセスでも発生しているのだろうか?
AssetManager.list から返ってきた結果を HashMap に格納するなどしてキャッシュすれば、2回目以降は高速にファイルリストが得られるが、1回目は時間がかかってしまう。
何もせずに同じパスで AssetManager.list を何度も呼んでいると、そこで多くの時間がかかってしまう。

投稿者 Takenori : 22:10 | コメント (0) | トラックバック (0)

2012年01月03日

吉里吉里Java :: InterCodeContext を分離

オリジナルの tTJSInterCodeContext と同様に、吉里吉里Java も InterCodeContext でコード生成と実行時のオブジェクト実体 ( VM ) の両方を兼ねている構造だったが、TJS2 バイトコードを読み書きしやすいようにするためと、見通しを改善するために、InterCodeGenerator と InterCodeObject に分離した ( TJS2の disassmbler も一体化していたんだけど、こっちも分離済み )。
分離に伴い、InterCodeGenerator から InterCodeObject を生成する時に少しのコピーとオブジェクトの置き換えが発生するために、少しコンパイル速度は低下しているはず。
ただ、PC 上では特に速度低下はみられなかった。
全体に占める割合からすると微々たる処理なのかもしれない。
後、ScriptBlock と言うのがあって、これはだいたいtjsファイルと1対1で対応するのだが、これもコンパイル処理と実行時に InterCodeObject を束ねる機能を兼ね備えている。
別に一体になっていてもいいが、見通しと実行時のメモリ使用量の削減 ( 微々たるものだが ) のために分離を考えている。

これらを分離すれば、Compiler + InterCodeGenerator → ScriptBlock + InterCodeObject と言う通常の TJS2 コンパイル動作と、バイトコードローダーから ScriptBlock + InterCodeObject を生成する処理を綺麗に分けられる。
生成元がTJS2コンパイラかTJS2バイトコードローダーかに関わらず、同じように実行時のオブジェクトを生成出来る形になる。
また、InterCodeGenerator は、CodeGenerator インターフェイスを継承するクラスにしようと思っている。
実際のバイトコードを生成するクラスを抽象化することで、TJS2 バイトコードのみでなく、Dalvik バイトコード等を生成しやすい構造にして行く狙い。
実際に Dalvik バイトコード を生成するクラスを作るかどうかはともかくとして。

このような処理によってコンパイル性能は若干低下するかも知れないが、リリース時はバイトコードを直接読む形になるであろう事から、それほど問題はないと考えている。
PC の場合は、あまり速度低下自体見られないようだし問題はないと思う。

投稿者 Takenori : 22:56 | コメント (0) | トラックバック (0)

吉里吉里Java :: OutOfMemory対策

Android で画像を読み込んだり、生成した時によく発生する OutOfMemoryError 。
本当にメモリが足りない時はどうしようもないが、解放されずに残っているオブジェクト等の影響で確保に失敗することがある。
このような状況を避けるためには、単純に以下のようにして1度目は OutOfMemoryError 例外をキャッチして、GC を実行し、再度確保を試みるようにすれば、成功することが多い。
当然、自身で多くのメモリを使用している場合はどうしようもない。

try {
 mImage = Bitmap.createBitmap( w, h, Bitmap.Config.ARGB_8888 );
} catch( OutOfMemoryError e ) {
 java.lang.System.gc();
 // 2回目は try-catch せず、2回目も失敗した時は諦める
 mImage = Bitmap.createBitmap( w, h, Bitmap.Config.ARGB_8888 );
}

後、Bitmap は不要になった時、bitmap.recycle(); して、null を代入しておいた方が良い。
recycle() は、ネイティブオブジェクト(ピクセルデータ) との関連を強制的に断ち切る。
Bitmap オブジェクトが他で参照されていても GC が発生したら、ピクセルデータは解放される。
当然、再使用しようとしたらエラーになる ( isRecycled で使えなくなっているかどうか確認できる )。

吉里吉里Java だと、上記の GC を試してみる前に、System.doCompact 処理を実行して、内部のキャッシュを参照解除 して、GC で解放されるようにする。
これで出来るだけメモリを空けて Bitmap の読み込みや生成が成功するようにする。
このような処理で OutOfMemoryError で落ちる確率は減るが、GC が走ると数百msec程度処理が止まる。
落ちるよりはマシだが、カクつく原因にはなる。

再読込等で生成しやすい画像データは、読み込んだ後 recycle() をあらかじめ実行して置いて、必要時にはisRecycled でまだ使えるかチェックして、使えなくなっている時は再度読み込んで必要な処理を行うことで、より OutOfMemoryError に陥る可能性を減らせるようだが、頻繁に再読み込みが発生すると困るので、このような処理は行っていない。
メモリ的に厳しいようであれば、このような処理も検討する。
実際問題として recycle() した後どの程度解放されてしまうのかによっては、普通に使用しても問題ないかも知れないが、ドキュメントを見る限り次に GC が実行されたら消えてしまうように思える。
2回目もメモリ確保に失敗した時は、ロードしやすいデータは recycle() してしまい、OutOfMemoryError で落ちる確率をより減らすような処理があった方がいいかもしれない。
ただ、その場合は今表示されているデータが解放されてしまい、ロード地獄に陥るかも知れないが。

投稿者 Takenori : 22:05 | コメント (0) | トラックバック (0)

2012年01月01日

吉里吉里Java :: Android での起動時間

Android で KAG3 がある程度動くようになったので、速度を計測した。

Xperia arc (Android 2.3.3) で測った結果

KAG3のコンパイルに 14秒。
KAGMainWindow のコンストラクタに 2秒。
吉里吉里の初期化全体で 19秒。

コンパイルとコンストラクタ以外の部分で 3秒かかっている計算になる。
TJS2 のバイトコード読み込みに対応して、事前コンパイルしておけば、起動は 5秒程度まで縮まるだろうか?
コンパイル時間にはトップレベルスクリプトの実行時間も入っているので、14秒が丸々なくなることはないだろうと思われるが。
2.2 未満の場合もっと時間がかかりはず。

まあ、何にしても 5秒程度であれば許容範囲だろう。
スプラッシュを出せばそれほど気にならない範囲ではある。
Dalvik バイトコードを出力しなくても、TJS2 の事前コンパイルのみでも何とかなりそうなことはわかった。

ただ、開発中は何度も再起動するので、起動時間の短縮は開発中にいろいろとがんばるはず。

投稿者 Takenori : 23:53 | コメント (0) | トラックバック (0)

2011年12月28日

吉里吉里Java :: direct threaded code

VM を素直に実装すると、switch-case で命令を振り分けて対応する処理をするような作りになる。
これを switch-case で振り分けるのではなく、命令(だいたい単なる整数)に処理が書かれた先のアドレスを埋め込み、そこへ直接ジャンプすることで、switch-case をなくす。
さらに、そこから次の命令に飛ぶ時、その命令の処理の末尾で直接次の命令の処理先へジャンプすることで、一度命令を振り分けるところに戻る処理を省ける。
この辺り threaded code や direct threaded code などと言うテクニックのようだ。
説明は、YARV Maniacs 【第 3 回】 命令ディスパッチの高速化を読むとわかりやすい。
後、Context threading と言うのもあるよう。

なるほど、そう言うテクニックがあるのかと思ったけど、Java では書けない。
C言語でも厳しい。
gcc だとラベルのアドレスを拾って、そこへ飛べる拡張があるようで、gcc なら C言語で書けるようだ。

高速化のために調べたものの、吉里吉里Java では使えそうにないことがわかった。
Java や Dalvik バイトコードを出力できるようにすれば大丈夫だけど、その場合、switch-case とかそう言う話ではなく、TJS2 のバイトコードではなくなってしまって、JVM や Dalvik VM で命令部分が処理されることになるんだけど。

その他参考リンク

SquirrelFish Extreme 高速化の話
direct threaded code
direct threadingでフィボナッチ計算
VM の高速化のために Context Threading を学ぶ - Scheme VM を書く

投稿者 Takenori : 21:13 | コメント (0) | トラックバック (0)

2011年12月23日

吉里吉里Java :: クラスのロードが遅い

KAGMainWindow のコンストラクタが遅い問題でいろいろと速度を計測していると、どうもクラスのロードが遅い様子。
ネイティブのWindow クラス ( JFrame を継承したクラス ) のロード自体に 120ms 程度かかっていたので、JFrame を new してみると最初の1回目だけ遅い。
そこで KAGMainWindow を2回作ってみると、2回目のコンストラクタは 162ms になった ( 2回同じところを通ることで特定部分についてはJIT等の影響があるかもしれない )。
オリジナルの 55ms に比べると3倍程度かかっている。

TJS2 のコンパイル時間と合わせて推測すると、純粋に処理時間としてはネイティブに比べて、Java は3倍程度なのだろうか?
ベンチマークで速度が出るのは、JIT によってネイティブコードになることで、30%程度の速度差で済んでいるのかもしれない。

クラスのロードが遅いとすると、Android では Window は違った傾向を示すと言うか、このタイミングで遅くなることはないはず。
Android は、最初に生成された Window を Activity と関連づけるようにする予定なので、KAGMainWindow のコンストラクタが呼ばれる時点では、既に Activity はロードされている。
だから、ここでクラスロードに時間がかかる可能性は低い。
ただ、クラスロード時間がないと思われる 162ms を基準にしたとしても、この時間の 80 倍時間がかかると 13秒もここでかかってしまう ( Activity 以外のクラスロードはあるのでもっと遅いかもしれない )。
スプラッシュを何個か出せばごまかせなくはないかもしれないけど、これは辛い。
Android の場合、Window 周りの処理やメソッドの多くは無効というか設定できなくなるので、この辺りで少しは高速化されるかもしれないが……
地道に最適化していくしかないか。
そのままでも動くけど遅い、KAG3 にある程度手を入れてプリプロセッサで切り分けて、Android 版は軽量化を図るとかになってしまうかもしれないな。
Android からすると要らない(無効な)処理が、KAG3 には入っているのでそれらを削れるだけでもだいぶ違うはず。
どうにもならなくなったら、Dalvik バイトコードへの AOT コンパイルか。

投稿者 Takenori : 19:11 | コメント (0) | トラックバック (0)