管理人版DGEN100A19を弄ってる。(SEGADRIVE)+(picodrive)
現在WRITEMEMORY関係はそのままで、READREMORY関係を一通りは終わらせた所。
が、
これがA18に比べて速いのか遅いのか、訳判らん。
速度は置いておいて、それ以前に問題点もある。BYTE系の命令をgenesisplusからサルベージした
時は問題なかったが、WORD系の命令をサルベージしたら、MW4で音が出なくなった。
unsigned md::s68k_readword(unsigned a)//WORD系はBYTEを二回流し込めばほぼOKなのだが、
//いろいろ細かい問題点があって、現実にはそれだけでは駄目だ。
//A18はなるべくBYTEに振っている。が、現実には殆どWORDで来るので(この部分はZ80は関係ないから)
//WORDの処理をきちんと作った方がパフォーマンスアップが期待出来る。
//現に「管理人版MD022」のZ80でWORDをBYTE風になるべく共通化させた所。
//パフォーマンスダウンしたので、これが根拠になっている。(「管理人版MD022」の#ifdef Z80_MEMTYPE_NEWという辺りを有効にしてみる)
//が、「管理人版DGEN100A19」の場合は、事情が異なるかもしれない。メモリルーチンを増やすより、
//減らしてキャッシュを有効活用した方がパフォーマンスアップするかも知れない。またPSPはBYTE(unsigned char)が
//PCに比べて速い様なので、この辺も有効活用できる。
//色々複雑な要因が絡んで、(チェックも多いし)一筋縄ではいかない。なかなか難しい所である。
{
switch( a & (7<<21) ) // 現在aはアドレス(address)
{
case (0<<21): /*[0][1][2][3] ROM */
case (1<<21):
return *((unsigned short*)&rom[(a) ]);
case (7<<21): /*[e][f] RAM */
return *((unsigned short*)&ram[(a)&0xffff]);
case (5<<21): /*[a][b] Z80 & I/O */
if(a <= 0xA0FFFF) /* for Z80 */
{
// if(z80busack == 1)
// {
// return (m68k_read_bus_16(a));
// }
// else
{
unsigned char temp;
switch(a & 0x6000)
{
case 0x0000: /* RAM */
case 0x2000:
temp = z80ram[a & 0x1FFF];
return (temp << 8 | temp);
case 0x4000: /* YM2612 */
temp = myfm_read(a & 3);
return (temp << 8 | temp);
}
}
ここ(z80busack)が原因らしい。(コメントで殺してある部分が無い事)
Z80関連の調停をきちんと実装しなければ駄目だ。
<続く>
■#?? ちょっと余談PSPの得意なコード、苦手なコード。
管理人が体験した範囲で、PCと比べてPSPの得意なコード、苦手なコード。を大雑把にまとめとく、
(っていうかまとめページです。)
アルゴリズムで稼ぐのが理想的だが、実装でもこれだけ稼げるじぇ。って例。
アルゴリズムだけで稼げって説教する奴は、精神論だけでなんとかなると思ってる体育教師と同じだ。
現実には両方とも必要なんだよ。
1.ええと始めにシフト。これはトロイ。PC>PSPだな。(この件はPCが速過ぎる)
2.次アドレス加算。これもトロイ。PC>PSPという事。
PCで
unsigned char *src;
lb[0] = table[(lb[0] << 8) |(*src++ | palette)];
lb[1] = table[(lb[1] << 8) |(*src++ | palette)];
lb[2] = table[(lb[2] << 8) |(*src++ | palette)];
lb[3] = table[(lb[3] << 8) |(*src++ | palette)];
lb[4] = table[(lb[4] << 8) |(*src++ | palette)];
lb[5] = table[(lb[5] << 8) |(*src++ | palette)];
lb[6] = table[(lb[6] << 8) |(*src++ | palette)];
lb[7] = table[(lb[7] << 8) |(*src | palette)];
この手のコードは、
unsigned char *src;
lb[0] = table[(lb[0] << 8) |(src[0] | palette)];
lb[1] = table[(lb[1] << 8) |(src[1] | palette)];
lb[2] = table[(lb[2] << 8) |(src[2] | palette)];
lb[3] = table[(lb[3] << 8) |(src[3] | palette)];
lb[4] = table[(lb[4] << 8) |(src[4] | palette)];
lb[5] = table[(lb[5] << 8) |(src[5] | palette)];
lb[6] = table[(lb[6] << 8) |(src[6] | palette)];
lb[7] = table[(lb[7] << 8) |(src[7] | palette)];
とするべきだ。
「配列+小さな値」を利用すれば、アセンブラコードになった場合に、
ポスト/プリ、インクリメントに変換される事が期待できるからだ。
PSPのGCCの最適化は、あくまで無駄なコードを取ることであって、
コード効率を上げてくれる訳ではない。
アドレスレジスタに変換されるよう記述してあれば、真面目にそのとおり変換してくれるし、最適化も効かない。
3.(2)と全く同じ話の裏返しだが、「配列+小さな値」。これは速い。PC<PSPという事。
管理人版DGEN100の場合。68000の「インテル・モトローラ変換」を、
#if 000
//efine GET_WORD( src ) (((*((unsigned short*)src) << 8)&0xFF00) | ((*((unsigned short*)src) >> 8)&0x00FF))
//#define GET_WORD( src ) ((*((unsigned short*)src) << 8) | (*((unsigned short*)src) >> 8))
#else
/* PSPではこっちの方が軽いじぇ。 */
static inline int GET_WORD(unsigned char *src)
{ return (src[0] << 8) | src[1]; }
#endif
下のタイプにしたら、パフォーマンスがあがった経験がある。
4.char型、これは速い。PC<PSPという事。
(3.)のサンプルをじっくり見れば判かる事だが、src[0]の0は、0を加算するコードになってはいないので、
安心して欲しい。(調べた訳じゃないよ。だって管理人PSPのアセンブラは一片も解らないし知らない)
C言語の都合上、そういう風にしか記述できない為だ。(これは特定のアセンブラコードに変換される事を期待した場合)
(↑いやこれは間違いだな「{ return ( *src << 8) | src[1]; }」でも良い筈。但し、判り難いが)
unsigned char *srcを用意しているが、くれぐれもアドレスレジスタに割り付ける事を期待してるのではない。
もしそんなコードになってしまったら、明らかにパフォーマンスが低下する筈である。
ここでは、アドレスレジスタを使用しないで、「ポスト/プリ、インクリメントに変換しろ」、と暗黙のうちに
言っているC言語のコードだ。
現実にそのようなコードに変換されたからこそ、パフォーマンスアップした。
■それから、ぜひ知っておいて欲しい技術として「align」がある。
これはCPUがメモリをアクセスする時に、「得意な場所」。と「苦手な場所」。がある。
これは、PCでもPSPでもある。速いプログラムを組む場合の常識だ。
「得意な場所」をアクセスさせれば、速い。
「苦手な場所」をアクセスさせれば、遅い。
ただそれだけの「違い」である。動作はまったく一緒だ。
■PSPは腐るほどメモリが乗っている。多分32MB(バイト)。(PS2が羨ましがる声が聞こえてきそう)
DGEN100等は、古い形式のコアという事もあり、おもむろに68000用に12MB(バイト)も配列で確保してしまう。
それ以外にもメモリはあちこち使用するから、実行時には結構メモリを圧迫してる筈である。
RINさんがなにやら、「GBのROMは最大8MBあってうんぬん」とメモリ心配をしていたから、
管理人は、「そおかあOSが、ガブって取っちゃって、使える分は、少ないのかな?」と思っていた。(誤解してた)
どおりで、最近の訳判らん程メモリ喰いのMAMEが動いちゃう訳だよ。MAME097っていえば、仮想メモリシステムが
とっくの昔に大幅拡張されてて、128ビットメモリとかプログラム/データー以外の領域とか(殆どのCPUは関係ない)
何十メガバイトって、実行時にメモリを確保してやっと動くんだよ。もし、昔のPC、例えばWin95の
頃の標準PC、メモリ32MBなら(多分PSPと同じ)最近のMAMEは絶対に動かない。
MAMEが実行時にメモリを、32MB以上、確保する事を要求してて、スワップじゃ対応出来ないからだ。
(プログラムが一気に確保する事を要求する)
■GCC400での「align」の具体的な書き方。
static unsigned char cart_rom[0x800000]; //0x400000[4MB](4*1024*1024)
を
static unsigned char cart_rom[0x800000] __attribute__((aligned(64))) ; //0x400000[4MB](4*1024*1024)
こうする。
この例では、8MB(バイト)おもむろに確保してますな。(コメントは違っちゃってる。がここは色々理由があってわざと間違えてある)
(DGENなんて12MB(バイト)このやり方で確保する)
mallocは現状管理人の所ではうまくいかないから、すべて「malloc→配列」変換してる。
mallocなんだから、freeも殺すの忘れずに。
この例の「64」というのは数字で、「64バイト境界に、配置してねお願い。」という意味。
ハッキリいって「32」でも違いはないと思う。が、「いいじゃん別に、メモリ余りまくってるんだし」、
「境界がたまたま近ければ、全く同じ結果になるんだし」、ね。
いくらPSP、charが速いとはいえ、alignが合ってなければ、片手落ちだよ。
例えば構造体のalign合わせも(こだわるんなら)必要だな。
まだ、管理人書いてないが、そのうち「md_sound」のFM音源の構造体もちゃんとalignをあわせるつもり。
■genesis plus のコードについて。
これは、速くなる実装と関係が無い話なのだが、全然関係ない訳ではない。
C言語の注意して欲しい機能のひとつとして、インクリメント/デクリメントがある。
正式名称は良くワカンナイが、
i++;
って奴だ。これは、
++i;
とは(似てるが)意味が違う。C言語の入門書等で、無闇に++i;を使いたがるものがあるが、
それは悪書なので、焼いて良し。
これはきちんとした理由がある。
#inclide <stdio.h>
int foo_before(void){
int i,a;
i=3;
a= i++; //←ここが違う(iを評価し、aに代入。評価後にiを加算)
return(a);
};
int foo_alter(void){
int i,a;
i=3;
a= ++i; //←ここが違う(iを評価前に加算。その後、aに代入。)
return(a);
};
void main(void){
printf("i++の結果は、%d \n",foo_before());
printf("++iの結果は、%d \n",foo_alter());
}
こんなプログラムがあったとしよう。(気分で書いたので間違ってるかも、でも意味はわかるでしょ)
この手は、「重箱の隅」と言われて嫌われちゃうんだが。えーと結果は、3、4、の筈。(まともなC言語ならね)
GCCの場合は、調べてみなくたって、3、4、の筈。それ以外ならC言語のバグだ。
(↑でも、この場合はやっぱ、4、4、かもな。整数加算では少し例が悪いかも知れない。
ポインタ加算でないと説明出来ないかも。a= (i++);の場合は必ず4になる筈だからな)
++iというのは、判った様に、特殊な用法だ。これは、「特殊な用法が場合によって必要」だから付いている機能であり、
普段から特殊な用法を使用して良い訳ではない。普段から++iと書くのはオオカミ少年でバグの元。
じゃあ普段が++iで特殊な用法がi++に統一しておけば、何も問題がないではないかと、思うだろう。
だがこれが問題がある。問題があるから、普段がi++なんである。
ちょっとジョークだが、こんな話もある。
a= i+++j;
さあ、これはどおいう意味だ。なんてね。
(終わった後のiとjの数値は、どっちが増えるか判る?現実には優先順位があり、結果は仕様上(式の評価の方向)きちんと1つに確定する)
それはともかく、普段が++iだとポインタ演算関連が非常にやり難い。そんなコードはバグの元。しかもalignとの
関連もある。せっかく0から、配列を確保してあるのに、だれが1から転送したいんだ。超ふるいプログラム
「FORTRAN」の移植じゃあるまいに。コード書いてみれば解かるよ。絶対的に++iが使いにくい事がね。
上の奴コピーすれば、(0ではなく)1からの転送で、
lb[0] = table[(lb[0] << 8) |(*++src | palette)];
とか書くんだぜ。C言語が、「あんさん、そいつは訳わかりまへんがな」とか言ってWARNING出すから、
lb[0] = table[(lb[0] << 8) |(*(++src) | palette)];
とか書き換なきゃなんないんだぜ。こういうのが好きなのは、大学教授とかその手の方々であって、
一般的なプログラマーは(知ってれば内心は)嫌がるに決まってる。手間ばかり増えて、バグばかり、増える。
genesis plusは++i育ちなので、殆どのケースでは問題ないものの。プログラマーはこういう事は常識である為、
ちと見るのに(神経が)疲れるコードだな。っていう話。(神経が疲れるのは良くないんだぞ、バグの元だ)
ううう、厳密に説明するのは、管理人の神経が疲れるからもうこの話はヤメ!(でも、ひと通り話したでしょ?)