標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第573回]

●やっぱり納得できません

画面表示の速度について、前回は一旦は納得したつもりだったのですが。
いや、だめだ。
やっぱり、まだ、納得ができません。

そりゃあ、Z80BASICはCPUクロック6MHz、8ビットCPUのZ80ですから、それなりに時間がかかるのは仕方がありません。
しかし、それにしても、このPRINT文は余りに遅すぎます。

たとえば、下のBASICプログラム。

10 A%=0
20 PRINT "a"
30 A%=A%+1
40 IF A%<1000 goto 20

これを実行しますと、なんと15秒から20秒もかかってしまいます。
いくらなんでも時間がかかりすぎでしょう。
しかも実行時間が毎回違うってのは、一体どういうこと?

ええ。もちろん今はcprintf()は使っておりません。
基本的に置き換えるべきところはすべてputc()に置き換えてしまいました。

それでいてこの始末。
一体全体、なんなのでしょーね。

こうなってきますと、やっぱり、納得がいくまで追求したくなってきてしまいます。

●どうも改行があやしい

実はあれこれやっているうちに気が付いたことがあります。
さきほどのプログラム、それを下のように変更しますと。

10 A%=0
20 PRINT "a";
30 A%=A%+1
40 IF A%<1000 goto 20

途端に実行速度が一気に上がって、2秒ほどで終了してしまいます。
さきほどのプログラムではaを1文字表示するごとに改行が行われるのですが、あとのプログラムでは改行されることなく、間も空けないで、びっしりベタに表示されます。
こんなぐあいです。

あ。
さきほどの最初のプログラムは文字aだけではなくて、実際には改行コードも送っています。
文字aのASCIIコードは61です。
ですから、61・0D・0Aの3バイトがZ80CPU→PIC18F14K50→USB(HID)→Windowsパソコンというように送られます。

そこで条件を合わせるために、この画面のプログラムでは改行コードを送る代わりに、3文字abcを連続して送っています。
3文字×1000=3000文字の送信、画面表示に約2秒程度ですから、まあそんなものか、と思います。
すると、改行コードが加わることで実行速度が1/10に落ちてしまう、というのは、どういうわけなのでしょう?

その追求の前に、もう少し、基礎を固めておきたいと思います。
まずはZ80BASICの実行速度を確認しておきたいと思います。
Z80BASICはインタプリタですから、ちょいと実行に時間がかかってしまいます。
それでは一体どのくらい、時間がかかっているのでしょうか?
それは、どうすれば、測定できるのでしょう?

まあ、それを測定するにはいろいろな方法があると思います。
でも、たまたま、Z80CPUとPIC18F14K50の間でデータを受け渡しするときの、STROBE信号とREADY/BUSY信号をオシロスコープで観測する、ということをしましたから、それを利用することにいたしましょう。



上(CH1)はZ80CPUからPIC18F14K50にデータを送るときのSTROBE信号です。
PIC18F14K50からのREADY信号は応答が速いせいなのか、ちょっとよく見えません。
STROBE信号は3文字分が固まっていて、その間隔はだいたい2msec程度です。
これが今回のBASICプログラムの実行速度です。

さきほどのプログラムをもう一度お見せします。

10 A%=0
20 PRINT "abc";
30 A%=A%+1
40 IF A%<1000 goto 20

行番号20のPRINT文、30の計算式、40のIF〜GOTO文までを1回実行するのに約2msecかかっています。
その1回の実行でPIC18F14K50には3バイトのデータが渡されます。
PIC18F14K50がホストにUSB(HID)でデータを送信するのは1msec間隔ですが、一度に約60バイトのデータを送ることができます。

もっとも[第570回]で説明しましたように、ホストへのデータの送信は単純ではなくて、ホストからの返信と組にして行われますから、1msecで約60バイトを送る、というわけではありません。
しかしそのことを考慮しても、2msecに3バイトというデータ転送速度はHIDの通信速度に比較して十分ゆっくりした速度ですから、その間にHIDでのデータ送出は済ませてしまうことができます。
ですからZ80BASICの側の実行時間は、3バイトの送信に約2msecかかる、と考えることができます。
すると3000文字の送信には約2秒かかります。

前回C++で書いた簡単なputc()のテストプログラムの実行結果は、0〜999までの数値の表示に約100msecかかりました。
3000文字には少し足りませんが、まあ大体のところではそんなものだと考えてよいでしょう。

結局、BASICの実行時間に約2秒かかって、実際の画面表示のためのputc()の実行時間はそれに比べると、ほんのわずかな時間で、ほとんど無視してもよいほどしかかからない、と考えることができますから、あとで実行したほうのBASICプログラム(改行無し表示)の実行時間が約2秒だった、ということとぴったり一致します。

すると問題は、改行コードの処理、ということになります。

C++で書いたプログラムzb2xは、画面に文字を表示すると同時に、ページモード表示のためのバッファと、ログファイルにもデータを書き込む処理を行います。
上の結果から考えて、その処理時間も無視できるほどのものにしかならない、と考えてよい、と思います。

実は、改行コードがある場合には、そのほかにページモードの処理とスクリーンエディタのためのデータ処理のために、putc()で79文字分の空白(スペースコード)を、画面に表示する、という処理が行われます。

改行コードがあるときと無いときとで、C++プログラムでの違いは、そこのところだけです。
それなら、その部分だけを抜き出して、実行してみたら、何かがわかるのでは?

そこで、下のプログラムを作って、実行してみました。

//putc test
//10/8/3 8/4
#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <string.h>
//
void main()
{
	int c,i;
	char wkbf[81];
	clock_t start,end;
	for(i=0;i<79;i++)wkbf[i]=0x20;
	wkbf[i]='\0';
  	start = clock();
	for(c=0;c<1000;c++)
		{
		for(i=0;i<79;i++)putc(wkbf[i],stdout);
		}
	end = clock();
  	printf("%dmsec\n",end-start);
}

さきほどの改行有りのPRINT文のプログラムでは改行コードの表示を1000回行っています。
そのうち画面の一番下まで表示が進んで、スクロールされるようになると、1回の改行ごとに、79文字分のスペースが画面に表示されます。
上のプログラムは、そのスペースを79文字分表示するところだけを、1000回実行して、その間の実行時間を表示させるものです。
実行させてみた結果、実行時間は約1300msecほどでした。

あれ?
おかしいじゃないの?

普通の文字表示と改行を含む文字表示とでは、この部分があるかないかの違いしかない、はずなのです。
どう考えたって、この部分で15秒ほどもかかっているはずなのですが…。

んでも、それもおかしい話で、なんたってこのテスト作業に使っているパソコンは、Celeron1.7GHzなんですから、この程度のプログラムの実行に15秒もかかるってのは、そっちの方が信じがたい話になってきてしまいます。

むむ。
こうなったら、もう少し、徹底して追及してみなくては。

/// CR/LF
void vcr()
        {int i,j,x,y;
         char wkbf[81];
         i=cursor/80;cursor=(i+1)*80;
         if(cursor==winend||cursor==Vsize&&winend==0)
                {for(i=0;i<79;i++){vram[i+winend]='\0';wkbf[i]=0x20;}
                 wkbf[i]='\0';cursor=winend;wintop=wintop+80;winend=winend+80;
                 x=wherex();y=wherey();
                 //printf("%s",wkbf);gotoxy(x,y);//need for win2k
                        start=clock();//test
                for(i=0;i<79;i++)putc(wkbf[i],stdout);
                        end=clock();//test
                        total=total+(end-start);//test
                        crcntr=crcntr+1;
                gotoxy(x,y);
                }

実際にZ80BASICの受けとして使っているzb2xプログラムの改行処理の一部です。
そのputc()の部分の実行時間を測定するためにclock()関数を埋め込みました。
そのように準備をしてから、BASICプログラムを実行させてみましたところ、下のようになりました。
(aばかりが1000個並んでいても仕方がありませんから、途中はカットしてあります)

>time=0msec,crcntr=0
.
    10 PRINT "start"
    20 A%=0:A=0
    30 PRINT "a"
    40 A%=A%+1
    50 IF A%<1000 GOTO 30
    60 PRINT "end"
>time=0msec,crcntr=0
r.
start
a
a
a
a
a
a
a
a
a
a



a
a
a
a
a
a
a
a
a
a
a
a
a
a
a
end

>time=15920msec,crcntr=992

おお。
出た。出ました。
確かに、79個のスペースを出力しているだけのputc()の部分の実行時間がトータルすると約16秒かかっています。
しかも一緒に実行したカウンタの値は992ですから、そのputc()がその回数(約1000回)繰り返し実行されたことに間違いはありません。

でも、でも、でもっ。
それって、どこかおかしくはありません?
だって、同じパソコンで、同じC++のプログラムで、同じ処理ルーチンを実行させているのに、一方は1秒ちょっとの実行時間なのに、もう一方は16秒もかかる、だなんて…。

両者は同じ条件なのに…。

お。お。お。
違いがありました。
わかった。わかりましたよっ。

その部分だけを抜き出して実行したときと、Z80BASICの「受け」として実行したときの違いが!
ほら。
あるじゃありませんか!

そうなのでした。
それしか考えられません。
Z80BASICの実行とともにputc()が実行されているために、おそらくUSB(HID)の受信が影響しているのではないか、と思われます。
putc()の構造がどうなっているのかはわかりませんが、[Ctrl][C]を受けて強制終了しますから、キーボードからの[Ctrl][C]の入力を見ていることは間違いありません。
私のパソコンのキーボードはUSBではありませんが、それでもおそらくキーボードの入力チェックとUSB(HID)の信号入力との間になんらかの関係ができてしまうのではないでしょうか。

その証拠に、上のBASICプログラムを実行中に[Ctrl]キーを押し続けていると、その時のtimeは900msecほどになってしまうのです!
このときの([Ctrl]キーを押し続けているときの)、BASICプログラムの実行は、2〜3秒ほどで終了してしまいます。

う。む。む。
つうことは、HIDが原因ではあるけれど、いわば、それはぬれぎぬみたいなものであって、HIDそのものに原因があるのではなくて、putc()やprintf()やcprintf()がおそらく[Ctrl][C]をチェックするときにキーボードとは関係のないHIDの影響を受けてしまうところに、本当の原因があるのではないか、と思います。

だとすると、これはもう、不可抗力みたいなもので、もうどうしようもないようです。
ともかく、そういう次第で、改行を含むPRINT文はちょいとばかり時間がかかってしまいます。
まあ、裏ワザ的な対策としまして、大量データの表示などの場合には、[Ctrl]キーを押しっぱなしにしておく、ということに致しましょう。
2010.8.4upload

前へ
次へ
ホームページトップへ戻る