もっとメモリを
サイズ削減のための闘い
ver.0.01で分かったこと。とにかくメモリが足りない。 本体だけで34Kも使っているようじゃ使いもんにならん。 なりふり構わずメモリ削減に走らなければ。
とりあえずどこから毒牙にかけるべきかというと、
ファイラー予約エリア
かな。まず。
ラピュータ開発のことならばここが必見 Ruputer Fanの中のドキュメント、 メモリマップ上の予約エリア を見てもらうと分かりますが、ラピュータには「ファイラー予約」なる謎の エリアがユーザエリアの前にあります。その量3.5KB。3.5KBなんて、普段だったら 蚤の涙なみのどうでもいい大きさですが、ラピュータではとんでもなく貴重です。
そしてこのエリア、どうも「予約されてない」らしいのです。 少なくとも現状のラピュータでは使ってしまっても問題がない。 将来的に問題が発生するかも知れませんが、「ラピュータにはもう将来はない」 ので(ひどい)、容赦なく利用します。
このエリアを使うようにするのは簡単で、CodeWarriorの場合、 Panasonic Linkerの設定の、
-Ttext 0x110100を-Ttext 0x10F300にするだけです。これで3.5KBは君のものだ!mallocとfree
次にどうにかするのがこいつら。 ver.0.01では、メモリの確保(malloc)と解放(free)の 実現方法は、nmruputer_a.cにあるように、以下の通り。#define xmalloc(size) malloc(size) #define xfree(ptr) free(ptr)まんまです。これで「礼儀正しい」mallocができます。 ですが、Wabaを実装するときには、こんなにちゃんとした、 メモリを浪費するmallocはいりません。なぜかというと、「Wabaはその一生の中で4回しかメモリを確保しない」 からです。その4回で確保するのは、
です(参考 dealing with memory)。
- オブジェクトヒープ
- クラスヒープ
- スタック
- ネイティブスタック
Wabaは、この4つを確保したあとはメモリ管理を自前で行います。 ですので、この4つが最初に確保されると、あとは自分が死ぬまでこれらを 放さずに使い続けます。
(Waba4Ruputerではあと一回、CLASSES.WRPファイルを読み込むときに mallocをしていますが、それでも計5回です。)
よって、mallocは、とにかく指定された量の連続したメモリを確保できればよく、 freeにいたっては実装する必要すらありません。すると、こいつらは 以下のように実装できます。
static uint32 mlcAddress = 0x116400; static void *xmalloc(uint32 size) { uchar *ptr = (uchar*)mlcAddress; mlcAddress += (size&0xfffffff0)+16; return ptr; } static void xfree(void *p) { }なかなかに横暴な作りですが、ラピュータ上ではうまく動いている、 ように見えます。ちなみに、メモリを切り出す先頭アドレス(0x116400)は どうやって決めるかというと、CodeWarriorがmakeの際にexfファイルと いっしょに吐き出すmapファイルを見て決めます。mapファイルとは、生成したexfファイルがどのようにメモリマップ上に 配置されるかを記したファイルです。 ちなみにWaba.mapの一部を抜粋すると、
COMMON 0x001162fa 0x2 C:\Metrowerks\Ruputer\RupSDK\lib\libpsdos.a(doserr.o) 0x001162fa DOSerr 0x001162fc _end=. 0x001162fc PROVIDE (end, .)となっているので、どうも0x001162fcまで使われているらしい。 ので、0x116400からmallocしてしまえ、ということで先頭アドレスが 決まっています。いい加減だけど、とりあえず動くし。(3/30現在、これで本当に正しいのかちょっと自信がない。たまにスーパークラスの クラス名の取得に失敗することがあり、その原因がこのいいかげんなメモリ取得の せいかもという気がちょっとします。)
小数禁止
次。とうとうVMのスペックに手をあげることにする。 floatのサポートをやめてみます。小数が分からないVM。
さすがにこれはおいそれと実行しづらく、後で戻せるようにしておきたいので、 UNSUPPORT_FLOATというのがdefineされていると、floatがサポートされないように なっています。詳しくはwaba.c参照。
具体的には、以下のJavaVM命令セットを無視します。
主な変更は、waba.cのexecuteMethod関数にしてます。 それ以外にもちょこちょことした変更はありますが。
- fconst_<f>
- fload, fload_<n>, faload
- fstore, fstore_<n>, fastore
- fadd, fsub, fmul, fdiv, frem, fneg
- i2f, f2i
- fcmpl, fcmpg
- freturn
ここまでやって
てな感じ。ver.0.01が34,400バイトだったので、約6.5KBの縮小。 あと、ファイラー予約部分を3.5KB侵食したので、合計ちょうど10KB 儲かった計算になる。これは大きい。
- WABA.EXF(WabaVM本体) 27,856バイト
新サンプル
として、ラインが動きまわるものを作ってみました。import waba.fx.*; import waba.ui.*; public class MainClass extends MainWindow { private final int FLOW_LINE_NMB = 10; private FlowLine[] fl = new FlowLine[FLOW_LINE_NMB]; private int flIdx; private int x1, y1, x2, y2; private int mx1, my1, mx2, my2; public void onStart() { flIdx = 0; x1 = Rnd.nextInt()&(63*256); y1 = Rnd.nextInt()&(63*256); x2 = Rnd.nextInt()&(63*256); y2 = Rnd.nextInt()&(63*256); mx1 = Rnd.nextInt()%768; my1 = Rnd.nextInt()%768; mx2 = Rnd.nextInt()%768; my2 = Rnd.nextInt()%768; } private final int WIDTH = 102*256, HEIGHT = 64*256; public void onPaint(Graphics g) { for ( int i=0 ; i<32 ; i++ ) { if ( fl[flIdx] != null ) { fl[flIdx].clear(g); } else { fl[flIdx] = new FlowLine(); } fl[flIdx].setPos(x1/256, y1/256, x2/256, y2/256); fl[flIdx].draw(g); x1 += mx1; y1 += my1; if ( x1 < 0 || x1 >= WIDTH ) { mx1 = -mx1; x1 += mx1*2; } if ( y1 < 0 || y1 >= HEIGHT ) { my1 = -my1; y1 += my1*2; } x2 += mx2; y2 += my2; if ( x2 < 0 || x2 >= WIDTH ) { mx2 = -mx2; x2 += mx2*2; } if ( y2 < 0 || y2 >= HEIGHT ) { my2 = -my2; y2 += my2*2; } flIdx++; if ( flIdx >= FLOW_LINE_NMB ) flIdx = 0; } } }ですが、これ、とても「動きまわる」と言えるほどの動作をしない。 端的に言って、「むちゃくちゃ遅い」。今後
ラインサンプルを作って分かったのですが、このVM「遅すぎ」ます。 性能の向上が図れるといいのですが、アセンブラにまで手を出す気はないし。 とりあえずQUICKBINDの復活かなあ。
もどる