長らく、「ベタテキストのまま実行する」のが流儀だった超ミニBASICですが、部分的に中間言語化してみることにしました。中間言語化したインタプリタが安定して動くようになったら標準版のほうに成果を反映して行こうと思います。
いままで中間言語化に躊躇していた最大の理由は、中間言語化すると LIST "<文字列>" による、文字列抽出リストができなくなることを恐れていたからなのですが、LISTコマンド実行の際、中間言語化された行の内容を一旦バッファに展開してから文字列の比較を行い、その行を出力するかしないか決めればいい、ということに気付きました。
ただ、当面全部のキーワードを中間言語化するつもりはありません。中間言語用コードとして16進で$01〜$1Fの範囲のコードを割り当てようと思っています。この範囲のコードは普通のテキスト中に現れることはなく、即座に中間言語であると判断できるからです。$20以上のコードも中間言語として使用するとなると、いろいろ処理が面倒くさくなりそうなので。
全部ではなく、部分的に中間言語化するとしたら何を中間言語化するのが効率的でしょうか? INITLCD文はプログラムの頭で一回実行するだけですから中間言語化してもほとんど意味がありません。PRINT文やLPRINT文は使用頻度は高いんですが、これらの文の速度はRS232Cや液晶の表示速度に制限されますので中間言語化してもあまり効果は期待できません。
やはり制御構文を中間言語化すべきではないでしょうか。以下のアセンブラリストを見てください。これはVer1.02bの、whileループをスキップしている処理の部分を抜粋したものですが、見ていると絶対に中間言語化したくなりますよね。WとかHとかいちいち1文字づつ比較しているんです。これが、1バイトの比較命令一発になれば・・・
WHLSKP MOV.B #1,R2L ; SKIP LEVEL WHLSLP BSR SKPQRL AND.B R0L,R0L ; END OF LINE ? BEQ WHLS2:8 ADDS #1,ER3 CMP #H'57,R0L ; 'W' BNE WHLSLP:8 MOV.B @ER3,R0L CMP.B #H'45,R0L ; 'E' BEQ WHLSWE:8 CMP.B #H'48,R0L ; 'H' BNE WHLSLP:8 ADDS #1,ER3 MOV.B @ER3,R0L CMP.B #H'49,R0L ; 'I' BNE WHLSLP:8 ADDS #1,ER3 MOV.B @ER3,R0L CMP.B #H'4C,R0L ; 'L' BNE WHLSLP:8 ADDS #1,ER3 MOV.B @ER3,R0L CMP.B #H'45,R0L ; 'E' BNE WHLSLP:8 ADDS #1,ER3 INC.B R2L BNE WHLSLP:8 BRA OUTWRK |
キーワード |
中間コード |
| |
IF( |
$17 | ||
}{ |
$18 | ||
} |
$19 | ||
FOR |
$1A | ||
NEXT |
$1B | ||
REPEAT |
$1C | ||
UNTIL |
$1D | ||
WHILE |
$1E | ||
WEND |
$1F |
中間言語化版と標準版(Ver1.02c)のスピードを比べてみました。実行環境はH8/3048 16MHzです。
テストプログラム |
| |
標準版 |
中間言語化版 | |
10 FOR I=0 TO 1000000 20 NEXT 30 ?"END." |
34秒 |
29秒 |
10 A=0:B=0 20 FOR I=0 TO 100000 30 IF(I AND 1) { 40 A=A+1 50 }{ 60 B=B+1 70 } 80 NEXT 90 ?"END." |
41秒 |
36秒 |
10 FOR I=0 TO 100000 20 REPEAT 30 BREAK 40 UNTIL 1 50 NEXT 60 ?"END." |
18秒 |
17秒 |
中間言語化第2弾です。新たに以下の二点の変更を行いました。
具体的にはPOKE文、BCLR文、BSET文、PEEK関数、PEEK%関数、PEEK#関数、BTST関数、BTST@関数が対象です。組み込み制御用途ではこれらの文、関数の使用頻度が高いので、中間言語化の効果は大きいと思われます。
中間コードは以下のものが追加されました。
キーワード |
中間コード |
BTST |
$12 |
PEEK |
$13 |
BCLR |
$14 |
BSET |
$15 |
POKE |
$16 |
半角空白が2個、4個、8個連続していた場合、それぞれを1バイトのコードに圧縮します。これにより、プログラム中に余計な空白を入れることによる速度低下やメモリ不足などのデメリットがかなり解消されますので、心おきなくプログラムを見やすくするためのインデント(字下げ)ができます。
なお、注釈中の空白は圧縮されますが、”(ダブルクオート)で囲まれている空白は圧縮しません。
圧縮された空白のコードは以下のようになります。
空白2個 |
$01 |
空白4個 |
$02 |
空白8個 |
$03 |
標準版(Ver1.02c)、3月2日版、4月11日版のスピードを比較してみました。実行環境はH8/3048 16MHzです。
テストプログラム |
| ||
標準版 |
3月2日版 |
4月11日版 | |
10 S=0 20 POKE $FFF800,0,0 30 FOR I=1 to 100000 40 BSET $FFF800,BTST@($FFF801,0) 50 POKE $FFF801,PEEK($FFF801)+1 60 S=S+PEEK%($FFF800) 70 NEXT 80 ?S |
84秒 |
82秒 |
78秒 |
中間言語化第3弾です。数値定数のバイナリ化を行いました。
いままで、数値定数もアスキーコードのままメモリに格納していたわけですが、たとえば "1000" なんてアスキー列を、ループ中で毎回 $03E8 とかに変換しているのかと思うと、その効率の悪さに頭が痛くなってきそうです。特にメモリやI/Oを操作するプログラムの場合、6桁の16進数が頻繁に出てくるわけで、それを入力した時点でバイナリ化しておけばかなりスピードアップすると思われます。
中間コードは以下のものが追加されました。
|
値の範囲 |
中間コード |
10進定数 |
10〜255 |
$04 + データ1バイト |
256〜65535 |
$05 + データ2バイト | |
65536以上 |
$06 + データ4バイト | |
16進定数 |
$00〜$FF |
$07 + データ1バイト |
$100〜$FFFF |
$08 + データ2バイト | |
$10000以上 |
$09 + データ4バイト |
旧バージョンではできた書き方 |
新バージョンでの書き方 |
10 A=$0800 20 B=$1000 LIST 10 A=$800 20 B=$1000 OK |
10 A= $800 20 B=$1000 LIST 10 A= $800 20 B=$1000 OK |
標準版(Ver1.02c)、3月2日版、4月11日版、5月15日版のスピードを比較してみました。実行環境はH8/3048 16MHzです。
テストプログラム |
| |||
標準版 |
3月2日版 |
4月11日版 |
5月15日版 | |
10 S=0 20 POKE $FFF800,0,0 30 FOR I=1 to 100000 40 BSET $FFF800,BTST@($FFF801,0) 50 POKE $FFF801,PEEK($FFF801)+1 60 S=S+PEEK%($FFF800) 70 NEXT 80 ?S |
84秒 |
82秒 |
78秒 |
65秒 |
これ以上の、「簡単なわりに効果の大きい中間言語化」はいまのところ思いつかないので、中間言語化の実験は一旦5月15日版で終わりとします。これ以上中間言語化を進めるなら、いっそのことBASICコンパイラにでもしたほうがいいような気がしますし。
もちろん、なにがなんでももう絶対これ以上中間言語化は進めない、ってわけではなく、なにか効果的な中間言語化を思いついたら実施します。
とりあえず、現時点での成果を標準版(3664版やSH版にも)に反映しようと思います。あと、いつになるかわかりませんが、「中間言語対応のマルチスレッド版」も作りたいです。