MachiKania

MachiKania BASIC 高速化テクニックあれこれ

2019年3月27日

MachiKania BASICは、インタープリターではなくコンパイラーです。なので、かなり高速に動作します。しかしながら、もっと速くコードの実行を行ないたいという場面もあるかも知れません。この記事では、開発者だから分かるBASICコード高速実行のテクニックについて、幾つかを紹介します。

長い名前の変数だからといって、動作が遅くなるわけではない

変数としては、A-Zの36個の物に加えて、USEVAR(もしくはFIELD, STATIC)で宣言することにより、最長6文字までの英数字文字列で名付けた変数も使えます。長い名前の変数を使った場合と、A-Zの変数を使った場合とで、実行速度に差はありません。

演算順位などについて

演算順位については、コンパイラーが順位を解釈しながらコンパイルするので、記述を変えても速度は変わりません。2のべき乗による乗除算は、シフト演算に置き換えた方が高速です。定数による演算は、まとめて実行した方が高速です。以下に、幾つか例を挙げます。矢印右のコードの方が、高速です。イコールの場合は、速度は変わりません。

「A+B*C」=「B*C+A」=「A+(B*C)」
「A*256」→「A<<8」
「A/256」→「A>>8」
「A%256」→「A-((A>>8)<<8)」→「A AND 0xFF」
「A+2+3」→「A+5」

シフト演算子「<<」「>>」は、四則演算子よりも優先度が低いので、注意して下さい。

文字列へのポインターを使って高速化

例えば、T$という文字列型変数を扱っているとしましょう。この場合、MachiKania BASICの仕様では、Tという整数型変数は、文字列領域へのポインター(文字列が格納されているRAMアドレス)を保持しています。この仕様を利用して、高速な動作をするコードを書くことが出来ます。以下に、幾つか例を挙げます。

「ASC(T$)」=「PEEK(T)」
「ASC(T$(P,1))」→「ASC(T$(P))」→「PEEK(T+P)」
「RETURN T$(P)」→「RETURN T+P 」
「T$=T$(0,P)+CHR$(C)+T$(P+1)」→「POKE T+P,C 」
「IF 0=STRNCMP(T$(P,1),CHR$(C),1) THEN 」→「IF PEEK(T+P)=C THEN 」

他には、例えば、以下の例のような文字列の連結などは多少時間のかかる操作です。

T$=""
FOR P=1 TO 10
  T$=T$+CHR$(RND() AND 255)
NEXT
PRINT T$

これを、次のように書けば高速化出来ます。

DIM T(2)
FOR P=0 TO 9
  POKE T+P,RND() AND 255
NEXT
PRINT T$

「DIM T(2)」の所で、12バイト(0から2までの3ワード)のヌルバイトを含む領域が確保され、その領域をPOKEステートメントで埋めることで、上の文字列連結と同じ結果を得ることが出来ます。配列も文字列と同じように、整数型変数値をポインターとして利用出来ます。なお、「RND() AND 255」のところ、「RND()」と書けばさらに高速化出来ます。CHR$()関数とPOKEステートメントの第二引数では、整数をバイト値として扱い、下位8ビットのみが有効だからです。またこの例は、DIMステートメントを用いることで、ヌルバイトを含む文字列領域を確保出来ることを示しています。

多次元配列が使える時は、それの方が一次元配列より速い

一次元配列を用いた次のコードよりも、

DIM A(11)
FOR Y=0 TO 3
  FOR X=0 TO 2
    A(Y*3+X)=X+Y
  NEXT
NEXT

多次元配列を用いた次のコードの方が、高速です。

DIM A(2,3)
FOR Y=0 TO 3
  FOR X=0 TO 2
    A(X,Y)=X+Y
  NEXT
NEXT

これは、上のコードでは乗算を行なってアドレスを計算しているのに対し、下のコードではそういった計算結果が予め多次元配列構造の中に含まれていて、改めて計算し直す必要がないためです。ただし、多次元配列の方が一次元配列よりも、より多くのメモリー領域を必要とすることに留意して下さい。

永久ループ

以下は、永久ループを様々な方法で構築する例です。

WHILE 1:WEND
DO WHILE 1:LOOP
DO:LOOP WHILE 1
DO:LOOP
LABEL LBL1:GOTO LBL1
100 GOTO 100

下の例になればなるほど、より高速です。具体的には、上から、7, 7, 6, 4, 4, 3 CPUサイクルでループします。下の二つはGOTO文を使っているので、現代のプログラミングでは余りふさわしくない方法かも知れません。なので、構造化プログラミングに於いては、「DO:LOOP」が最速ということになります。ただし「WHILE 1:WEND」と比較しても、ループ当り3CPUサイクルの差しかないので、よほど高速化が必要なルーチンでない限り、余り気にすることはないと思います。

FOR ループ

以下は、変数Iの値を1から10まで変化させながらループする例です。

I=1:WHILE I<=10
I=I+1:WEND

I=1:DO
I=I+1:LOOP UNTIL I=11

FOR I=1 TO 10
NEXT

3つの例を挙げましたが、下になればなるほどより高速で、初期化などを除いたループ部分のCPUサイクル消費はそれぞれ、28, 27, 17です。普通にFORステートメントを用いるのが、最速です。

ビデオ出力を止めて高速化

高速に処理するために最も効果的なのが、ビデオ出力を止めることです。MachiKaniaでは、1チップでプログラムの実行だけではなく、ビデオの出力、キーボード入力、音楽再生など、全ての作業を行なっています。特に、ビデオ信号の出力はCPUのリソースをかなり消費しますので、これを止めるとプログラムの実行が速くなります。次のようなコードでこれを行なうことが出来ます。

SYSTEM 200,0
  時間がかかる処理
SYETEM 200,1

ビデオ出力を止めると、Type Mで3.4倍、Type Zで5.2倍、高速に処理が出来るようになります。

(このページは、新たな例に気づき次第、順次追加・修正します)

コメント

n (2019年3月30日 13:59:21)

katsumi様

はじめまして、nです。

A%256は、A-((A>>8)<<8)ではないですか?
(255とのアンド演算のほうが速いかもしれません)

Katsumi (2019年4月1日 10:49:02)

nさん

ご指摘の通りです。修正しました。

コメント送信