いまさらベーシックマスターの開発環境を作ってみる(18) NAKAMOZU Tiny BASICを高速化する

2024/07/17BASICMASTER, 昔のパソコン

いまさらベーシックマスターの開発環境を作ってみる(17) プロファイラを作ってみる | ず@沖縄で作った簡易プロファイラを使ってNAKAMOZU Tiny BASIC(以下NTB)を高速化してみた。NTBは、下記連載で移植したものだ。

NAKAMOZU Tiny BASICのASCII誌の掲載記事・ソースコードは作者のサイトで公開されている。


1文字読み込みルーチンが多く呼ばれている

インタプリタなので当然のことなのだが、1文字読むルーチン(PKUP)が一番多く呼ばれている。

PKUPは1文字読んで区切り文字かどうかを調べてそれを返すルーチンだ。なので、多く呼ばれるのは仕方ないのだが、多すぎる。どうもダブって読んでいる部分があるようだ。


*                               ;(PICKUP)
ICPKUP  INX
PKUP    LDA A   0,X             ; スペース読み飛ばす
        CMP A   #' '
        BEQ     ICPKUP          ; 区切りチェック
        TST A                   ; 区切り $00 Z=1 C=0
        BEQ     RTN6            ; 区切り ':' Z=1 C=1
        CMP A   #':'            ; 他         Z=0 C=1
TBL     SEC
RTN6    RTS


PICKUPの情報を捨ててしまっている

2項演算子の処理を行なっているルーチンの、項を処理した後に演算子があるかどうかを調べている部分で、区切り情報を使っていない。
そのため最後の項のときに処理が無駄になっている。

たとえば、 A=1 のような単純代入の時に何度も演算子チェックをしてしまう (A=1*か? 1/か? 1+か? のように)。

下記は加減算の処理を行うサブルーチンの一部だが、BSR PKUPのあとCMP Aで’+’をチェックしていて、PICKUPの戻り値が使われていない。
これは乗除算、関係演算などの処理でも同様。


EX3     BSR     TM1
EX4     LDX     XS
        BSR     PKUP
        CMP A   #'+'
        BNE     EX6
        BSR     TM1             ; 加算
EX5     BSR     EX8
        BRA     EX4
EX6     CMP A   #'-'

BSR PKUPの次にBEQを入れて、行末や’:’の時は早めに処理を打ち切るようにした。各レベルの処理が数バイトずつ長くなったが、効果大である。

なるべく割り込みを止めない

6800はポインタとして使えるレジスタが少ないので、SPをインデックスレジスタとして使うテクニックが使われている。もちろん、SPを使っている間に割り込みが来ると暴走してしまうので、SEIとCLIで挟んで割り込みが発生しないようにする。

BASICMASTERでは、割り込みを止めると2つの問題が発生する。

1つ目はタイマーが止まってしまうことだ。CRTの垂直同期に合わせて1/60の割り込みを発生させて、それをソフトウェアでカウントアップすることでタイマー処理を行なっている。割り込みを止めると、タイマーが進まなくなる。

2つ目はBREAKボタンがNMI割り込みであることだ。NMIなのでSEIでは止まらない。ハード的にはBREAKを無視できるようになっているが、そのためにはI/O操作が必要で、専用のモニタルーチンを呼び出す必要がある。せっかくSPを使って高速化してるのに、時間のかかる割り込み禁止・解除のサブルーチンを呼んだのでは意味がない。

ということで、PSHX/PULXルーチンをSPを使わない形に変更してみた。このルーチンはGOSUB/DOで使われていて、割と使用頻度が高い。

このルーチンがSPを使っているのは、6800のIXレジスタは、負のオフセットが使えない為でもある。改良後のルーチンはIXをあらかじめマイナス方向にずらすテクニックを使っている。

下記のようにPSHX/PULX用スタックポインタの使い方を変えた。元はXSPの指す先はPSHX済みのデータがある場所だが、新しい方は次にPSHXする場所になっている。
(UNTIL処理でも、LDX 0,X が LDX2,Xになっている)

若干PSHXが長くなるが、PULXやUNTが短くなっているので問題ないと思う。


UNT     LDX     XSP                 ; 不成立なら
        LDX     0,X                 ;  IXSTACKの内容を
        INX                         ;  取りだし、そのADRSへ
        RTS
(中略)
PSHX    STS     SPS
        JSR     NMISEI
        TXS
        LDX     XSP             ; IXスタックポインタ
        CPX     #STACK+3
        BEQ     ERR10
        DEX
        DEX
        STS     0,X
PX1     STX     XSP
        TSX
        LDS     SPS
        JMP     NMICLI
*                               ;(IXスタックPUL)
PULX    LDX     XSP
        CPX     #IXSTCK
        BEQ     ERR10
        STS     SPS
        JSR     NMISEI
        LDS     0,X
        INX
        INX
        BRA     PX1
[/code]
[code lang="asm"]
UNT     LDX     XSP                 ; 不成立なら
        LDX     2,X                 ;  IXSTACKの内容を取りだし、そのADRSへ
        RTS
(中略)
PSHX    PSHB
        STX     XSPWK
        LDX     XSP
        CPX     #STACK+3-2
        BEQ     ERR10
        LDAB    XSPWK+1
        STAB    1,X
        LDAB    XSPWK
        STAB    0,X
        DEX
        DEX
        STX     XSP
        LDX     XSPWK
        PULB
        RTS
*                               ;(IXスタックPUL)
PULX    LDX     XSP
        CPX     #IXSTCK-2
        BEQ     ERR10
        INX
        INX
        STX     XSP
        LDX     0,X
        RTS

予約語のサーチでもSPが使われているのだが、こちらはかなり面倒くさいので後回し。

どれぐらい速くなったか

ASCIIベンチマークや、円周率計算の速度の結果から10%-15%ほど速度が上がっている。

GOTO/GOSUBの行番号検索や、予約語の検索が遅いんだよなあ。アイディアはあるんだけど、メモリ喰うのでどこまで頑張るか……クロスコンパイラ作った方が早い気もするし。

高速化版はgithubに反映してあります。



続く