ブロック転送

1~100 を足すプログラムで、先に改良が進んだのは 6502 でした。

一時期 6502 の方が速そうに思えたので、「Z80 に花を持たせようと 16bit 演算&ループを使う題材にしたのだけど」と書いたところ、「Z80 に花を持たせるなら、ブロック転送はどうでしょう」という意見がありました。


目次

Z80 vs 6502 トップページ(別ページ)

概要

Z80基本

ループ展開

6502基本

ループ展開さらに展開無駄の削除

速度比較



概要

ブロック転送か…ちょっとずるい気もしますが、実用的な例題でもあります。

ブロック転送とは、連続したメモリ内容を、別のアドレスにコピーすることを言います。


別記事書きましたが、ゲームを作る際には「画面表示タイミング」を気にしながら画面を書き変える必要がありました。

実際の VRAM と同じサイズのメモリを用意しておき、そこを仮想画面としてゲームの画面を作っておきます。

そして、画面が垂直帰線期間(何も表示しない時間)になった時に、そのメモリ内容を VRAM に転送するのです。


ファミコンでは、スプライトの設定がちょうど 256byte に収まる設計だったため、256バイトを一気に転送したりしました。

6502 は 256 バイト以上のメモリを一括して扱うのが苦手なのですが、ファミコンのハードはうまく設計されていて、256バイトの転送で済むようにしてあるのですね。


MSX では Z80 から直接 VRAM を操作することはできなかったのですが、例題ですので


・400H から 200H byte を、600H にブロック転送する


を考えてみます。


改良案は、ページ下部のコメント欄や、メールで送って下さい。待っています。(SPAM対策アドレス表記使用)


・ローカルルール

ループ展開は、1~100を足すプログラムと同様、Z80 は30バイト、6502 は 40バイト以内に収めるものとします。


Z80基本

Z80ではこんな感じ。


	LD HL,400H ; 10 (11)
	LD BC,200H ; 10 (11)
	LD DE,600H ; 10 (11)
	LDIR ; 21 / 16 ( 23 / 18)

LDIR は、ブロック転送命令です。HL から BC バイトを DE に転送します。

1バイト転送ごとに、 21 (23) クロックかかりますが、最後の1バイトだけは 16 (18) バイトで終わります。


速度は、前処理が 33クロック、LDIR が…1命令で 511*23 + 18 = 11771クロック、合計 11804 クロックですね。


これは、おそらく改良の余地のないプログラム。

(実質的に LDIR 命令1つで終わるプログラムで、他の部分はその前処理にすぎないため)


ループ展開

2014.8.5午前追記

改良の余地がない、と思っていたら、しっかり改良版が投稿されました…。


	LD HL,400H ; 10 (11)
	LD BC,200H ; 10 (11)
	LD DE,600H ; 10 (11)
LOOP:
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	LDI ; 16 (18)
	JP PE,LOOP ; 10 / 10 (11 / 11)

LDIR は、「自分自身へのジャンプ」を含んだ命令です。JR による条件分岐を見るとわかりますが、分岐する場合としない場合の時間差は 5クロック。LDIR でも、繰り返している間と最後の1回で、5クロックの差があります。

LDI は、「自分自身に分岐しない」LDIR です。なので、常に「最後の1回」の実行時間しかかかりませんが、LDIR と同じ動作をしています。

これを並べてループ展開すると、LDIR より速くなる、という構造。

LDI は、BC レジスタを減算していて、0 になるまで P フラグを 1 にしています。JP PE では、この時だけジャンプします。


LDI と JP PE を使うより、LDIR の方が速いから…と思っていたのだけど、ループ展開すれば速い、という当然のことに気付いてませんでしたね。

前処理が 33クロック、ループ中は 18クロックの命令を 8 個並べ、最後に 11 クロックのジャンプがあります。これが 64 回ループ。

(18*8+11)*64 = 9920 クロックです。

総計は 9953 クロック。ちなみに、プログラムサイズは 28バイトです。


6502基本

6502ではこうです。


	LDX #0 ; 2
LOOP:
	LDA $400,X ; 4
	STA $600,X ; 5
	LDA $500,X ; 4
	STA $700,X ; 5
	DEX ; 2
	BNE LOOP ; 3 / 2

6502 にはブロック転送命令もないですし、256 を超えるアドレスの扱いも苦手です。

そこで、512バイトを 256バイト 2つにわけて、ループの中で同時に転送しています。


転送方法も、A レジスタに読み出して、改めて別の個所に書き込むだけ。地味な作業です。


ループ内が 23 クロックで、最後だけ1クロック少ないです。ループ回数は 256回。前処理は2クロックです。


2 + 23*256 - 1 = 5889 クロック。


ループ展開

2014.8.5午前追記

Z80 にループ展開版が来ましたので、6502 にも作ってみましょう。…といっても、元々微妙にループ展開したような構造だったので、ループの回数を減らして内部を増やすだけ。


	LDX #7F ; 2
LOOP:
	LDA $400,X ; 4
	STA $600,X ; 5
	LDA $480,X ; 4
	STA $680,X ; 5
	LDA $500,X ; 4
	STA $700,X ; 5
	LDA $580,X ; 4
	STA $780,X ; 5
	DEX ; 2
	BPL LOOP ; 3 / 2

合計 29 バイト。40バイトをループ展開の制限としていますが、もう1段階の展開はできません。

LDA と STA で9クロック、の組み合わせが4回。そして、X のデクリメント(DEX)とジャンプで、5 クロック。これを 128回繰り返します。

(9*4+5)*128 -1 = 5247 クロック

最後に 1 引いているのは、ループ末尾のジャンプ命令は実行されないためです。


次ページ: さらに展開


1 2 次ページ

(ページ作成 2014-08-03)
(最終更新 2014-08-10)
第2版 …他の版 初版

前記事:掛け算     戻る     次記事:ランダムアクセス
トップページへ

-- share --

0000

-- follow --




- Reverse Link -