掛け算
目次
HL活用
2014.8.5 午前追記
Z80 は 16bit レジスタを何本か持ちますが、8bit CPU です。そのため、16bit 命令は使用に様々な制限があります。
HL 以外は計算ができない、というのもその一つ。ここまでのプログラムでは、BC を2倍するのに、C と B をそれぞれシフトしていました。
では、BC を HL にしてしまえば、 ADD HL,HL で簡単に2倍できるよ、というのが以下のプログラム。
LD DE,0 ; 10 (11)
LD A,100 ; 7 (8)
LD HL,101 ; 10 (11)
OR A ; 4 (5)
JP NZ,START; 10 (11)
EX DE,HL ; 4 (5)
JR END ; 12 (13)
LOOP1:
EX DE,HL ; 4 (5)
LOOP2:
ADD HL,HL ; 11 (12)
START:
RRA ; 4 (5)
JR NC,LOOP2; 12 / 7 (13 / 8)
EX DE,HL ; 4 (5)
ADD HL,DE ; 11 (12)
JP NZ,LOOP1; 10 / 10 (11 / 11)
END:
SRL H ; 8(10)
RR L ; 8(10)
BC の代わりに HL を使っていますが、最終結果にも「どんどん足していく」必要があるので、やはりHL を使っています。
Z80 は HL と DE の内容を瞬時に入れ替える機能(EX DE,HL)があり、これを活用しているのです。
そのため、前処理時点では、DE を 0 にしています。この時点では、DE が「結果」で、プログラム中で必要に応じて HL と入れ替わります。
前処理が 46クロック、後処理が 20 クロック。
計算ループは、加算有りが 58 クロック、加算無しが 30 クロック。最後は 17クロック速くなります。
総計は 46 + 58*3 + 30*4 -17 + 20 = 343 クロック
加算があるときはレジスタ切り替えの手間が発生するため、どうしても遅くなってしまう、というのが残念なところ。
しかし、機能の制限から必然的に使用するレジスタが決められている、という構造は美しいです。
参考記録
2014.8.8午後追記
GORRY 氏から投稿いただきました。記録更新ではないのですが、非常に面白いので参考記録として記しておきます。
考察も含めて面白いので、プログラムは氏のページを参照してください。
HL 活用プログラムは高速なのですが、実は高速なのは 乗数を 100 に特化しているから、という側面もあります。加算有りと加算無しの差が激しすぎるのね。
氏の作ったプログラムは、乗数 100 では遅いものの、一般化した際に HL 活用プログラムよりも高速になるようにしてあります。本当は、そういうのが「速いコード」と言えるのだと思う。
この企画の最初のページにもあるように、厳密な速度測定は不可能なのですね。ルールがなければ比較にならないし、ルールを作れば、そのルール「だけ」への最適化が避けられない。ここは楽しめればよいので、乗数 100 で速いコードで比較する、で良いと思っています。(ただし、他の乗数でも動かなくてはならない)
ループを8展開した版では、乗数 100 の場合でも速度を更新(292クロック)しているのですが、余りに現実的ではないコードなので、ここでは参考記録にとどめます。
以下余談。
GORRY 氏は、マイコンBASICマガジンと言う雑誌でライターをしていた方です。凄腕プログラマ。
僕は当時熱心な読者だったので、投稿いただき光栄に思っています。
Twitter でZ80に対するのは6809という認識を呟いておられました。Dr.D のマシン語寺子屋で機械語を覚えた僕としても、そう思います。
でも、今回は「ファミコンは MSX の半分の周波数で動いているから、速度も遅かった」と思っている人がいるようなので、反証したかったのが発端です。
Z80 のライバルは 6809 なのだけど、MSX のライバルは FM-7 ではなく、ファミコンだったという認識。
変数共用
2014.8.8午後追記
後で書く(でも先に公表された)6502 の変数共用版を移植したものです。
実は、僕も移植を試みたのですが、うまくできないなぁ、と思っていました。上手な人が処理すればちゃんとできるものですね。
LD DE,101 ; 10 (11);
LD HL,100 *256 ; 10 (11)
LD B,2 ; 7 (8)
LOOP:
ADD HL,HL ; 11 (12)
JR NC,SKIP1; 12 / 7 (13 / 8)
ADD HL,DE ; 11 (12)
SKIP1:
ADD HL,HL ; 11 (12)
JR NC,SKIP2; 12 / 7 (13 / 8)
ADD HL,DE ; 11 (12)
SKIP2:
ADD HL,HL ; 11 (12)
JR NC,SKIP3; 12 / 7 (13 / 8)
ADD HL,DE ; 11 (12)
SKIP3:
ADD HL,HL ; 11 (12)
JR NC,SKIP4; 12 / 7 (13 / 8)
ADD HL,DE ; 11 (12)
SKIP4:
DJNZ LOOP ; 13 / 8 (14 / 9)
SRL H ; 8 (10)
RR L ; 8 (10)
D は常に 0 。E に被乗数が入っています。乗数は H に入っていて、結果は HL に入ります。
乗数が H に入っているのに結果も HL に入れられる理由は、 6502 版の説明を読んで (^^;
前処理部分、投稿では公式 n*(n+1)/2 に厳密に作られていて、100 をコピーしてから+1 してました。他のプログラムで「100 と 101」を直接入れていますので、同じように変更しました。(コードサイズは変わらず、こちらの方が速い)
ローカルルールを定める前だったので、他の例題のローカルルールに従い、30バイトで作られています。この例題だけローカルルールを変えて定めたのは、30バイトでは難しそうだと思ったからなのだけど、ちゃんと収まっている。すごい。
前処理が 30クロック。後処理が 20 クロック。
筆算1段分の計算が、加算有り 32クロック、加算無し 25クロック。ループ展開されているので、4回目に 14クロックで先頭に戻り、8回目には 9クロックで後処理に進みます。
30 + 20 + 32*3 + 25*5 + 14 + 9 = 294
総計 294クロックです。