2011年04月13日


広告


NP2 for PSP 高速化実験(その2)

そんな訳で、しばらくSNES9Xをいじっていたので、今回はNP2 for PSPの高速化について少し実験してみた。

まずは、最新のv0.38の性能を確認しておきます。性能測定についてはNP2 for PSP高速化実験(その1)を参照してください。

ver0.38No Wait offNo Wait on
1回目8994983686
2回目8955283942

あれ、v0.37より微妙に遅くなっていますね。遅くなるような処理いれていないはずだけど。
とりあえず、これを基準値とします。1tickで1/1000秒なので、1000tickで1秒です。
No Wait Offで計測し、PC-98実機で動かしたときと同じ60秒が目標です。

それでは、いってみましょう。まずはgprofでプロファイリングしたところ、時間がかかっているのは、メモリのread/write、calcratechannel()とかsound_pcmunlock()はサウンド関係、ea_disp16()とかjnz_short()とかはCPUコア関係。
グラフィック関係は、以下のsdraw16p_2がワーストだが、全体の1.53%で大したことはなさそう。

Each sample counts as 0.001 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
17.89 46.45 46.45 54203281 0.00 0.00 memp_read16
9.66 71.53 25.08 212057550 0.00 0.00 memp_read8
8.37 93.25 21.73 12691803 0.00 0.00 calcratechannel
3.97 103.57 10.31 3699772 0.00 0.00 memp_write16
3.75 113.30 9.73 14843519 0.00 0.00 ea_disp16
3.18 121.56 8.26 9207 0.00 0.00 sound_pcmunlock
2.91 129.12 7.56 _jnz_short
2.66 136.03 6.92 _calc_ea16_i8
:
1.53 155.36 3.97 497 0.01 0.01 sdraw16p_2


メモリ関連を含め、CPUコアあたりが処理の大半を占めていて、ここが高速化のカギとなりそうです。

ところでざっくりとだが、np2を含め多くのエミュレータは、以下の様にメモリ上から命令コードを読み込んで、関数のポインタ配列から飛び先を選んで関数コールしている。

void( *func[])() = {op1, op2, op3};

void op1() {
//命令その1
}
void op2() {
//命令その2
}
void op3() {
//命令その3
}

while () {
func[*pc++]();
}


これを、labelの飛び先アドレスを変数にして、gotoジャンプができるというgccの拡張機能を使って書き換えてみる。

void *label[] = {&&op1, &&op2, &&op3};

while () {
ret:
retlabel = &&ret;
goto *label[*pc++];
}
op1:
//命令1
goto *retlabel
op2:
//命令2
goto *retlabel
op3:
//命令3
goto *retlabel


こうすると、関数コールに伴うオーバーヘッド(スタックの確保やレジスタの値の保存、復帰等)を回避でき、
高速化が期待できます。

で、実装して計測してみた。コードの変更量が半端なく、苦労した割には効果はいまいち。
あ、いじったのはi286のみ。v30はとりあえず切り捨て。i386のNP21はまったくノータッチ。NP21についてはお察しください。

i286命令実行部のラベルジャンプ化No Wait offNo Wait on
1回目8588479709
2回目8595980532


ラベルジャンプ化後のプロファイル。CPUの各命令処理は関数ではなくなったので、プロファイラからは見えなくなる。

Each sample counts as 0.001 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
27.02 37.83 37.83 45161057 0.00 0.00 memp_read16
12.55 55.41 17.57 10172873 0.00 0.00 calcratechannel
10.74 70.44 15.03 19059542 0.00 0.00 calc_ea_dst
8.50 82.34 11.90 184453464 0.00 0.00 memp_read8
6.64 91.64 9.30 10707068 0.00 0.00 memp_write16
5.70 99.62 7.98 6900 0.00 0.00 sound_pcmunlock
3.83 104.99 5.37 6 0.89 0.89 memf800_rd8
2.98 109.16 4.17 554 0.01 0.01 sdraw16p_2
2.74 112.99 3.83 6555078 0.00 0.00 memp_write8



memp_read16がワースト。これのcall graphを見てみると、i286c()からの呼び出しと、calc_ea_dst()からの呼び出しが大半をしめている。

[4] 62.2 0.21 86.88 280881 i286c [4]
15.03 12.40 19059542/19059542 calc_ea_dst [6]
25.57 0.00 30525473/45161057 memp_read16 [5]

[6] 19.6 15.03 12.40 19059542 calc_ea_dst [6]
12.24 0.00 14609446/45161057 memp_read16 [5]
0.16 0.00 2466354/184453464 memp_read8 [9]

memp_read16()は16ビットの読み込みに以下の様なマクロを使っている。

#define LOADINTELWORD(a) (((UINT16)(a)[0]) | ((UINT16)(a)[1] << 8))

これを、以下の様に書き換えてみる。

#define LOADINTELWORD(a) (((unsigned int)(a) & 1)? \
((UINT16)(a)[0]) | ((UINT16)(a)[1] << 8) : *(UINT16 *)(a))


結果失敗。これは元に戻した。
LOADINTELWORDの書き換えNo Wait offNo Wait on
1回目8791582310


i286c()のGETPCBYTE()を展開してみる
i286c()のGETPCBYTE()を展開No Wait offNo Wait on
1回目8507879216

ただし、適当に修正したので、プログラムコードがHIMEM領域とかに置かれていると、オーバーヘッドが増える。

そして、グラフィック周りで上位に来ていたsdraw16p_系の関数の無駄を見直し。
sdraw16p_系の関数見直しNo Wait offNo Wait on
1回目8380477336


とりあえず今回はおよそ6.5%の高速化となった。微妙。

今回の成果はv0.39として後ほどリリースします。




タグ:NP2 for PSP
posted by ひっそりぃ at 2011/04/13 00:33 | Comment(1) | TrackBack(0) | NP2 for PSP | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
最近になって PSP-3000(北米版)を購入して遊んでおり、こちらの NP2 for PSP も使わさせて頂いています。

高速化についてちょっと思ったのですが、PSP はリトルエンディアンらしい? ので common.h に定義されているマクロはリトルエンディアンに特化して良いのでは?
(元のソースはプラットフォームがビッグエンディアンでも動作可能な様な汎用コードになっているようですが・・・)

だから psp 専用ということで以下の様なコードでも良くないかと・・・ちょろっと思いました。(過ちを犯しているかも知れませんが、その時は笑って下さい。)

#define LOADINTELDWORD(a) (*(UINT32 *)(a))

#define LOADINTELWORD(a) (*(UINT16 *)(a))

#define STOREINTELDWORD(a, b) *(UINT32 *)(a) = (UINT32)(b)

#define STOREINTELWORD(a, b) *(UINT16 *)(a) = (UINT16)(b)
Posted by ちょろっと at 2011年12月18日 11:45
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:


この記事へのトラックバック
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。