
2025/12/14 21:04
Efficient Basic Coding for the ZX Spectrum (2020)
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
ZX SpectrumのROM BASICは、各プログラム行を「行番号 2 バイト」「長さ 2 バイト」「終端 0x0D」の順で格納します。1 行にはエラーが発生する前に最大127 のステートメントを含めることができます。通常、編集時は行番号を 1–9999 に限定していますが、POKE を使うと 0 や 9999 より大きい番号の行を作成できます。しかしこれにより問題が生じます(GO TO/RESTORE が 32767 超で失敗、GO SUB が 15871 超で失敗、LIST と編集が壊れる)。
インタプリタは行アドレス表を保持せず、GO TO・GO SUB・NEXT・FN 等のジャンプ命令に遭遇するたびにプログラム全体を先頭からスキャンします。これは前方の各行につき約 71 µs のコストがかかります。そのため、多くのジャンプや頻繁な呼び出しを含むコードを最初に配置すると、実行時間から数百ミリ秒を削減できます。
ZX‑Basicus は最適化を支援します。
でどこに時間が使われているか表示します。--profile
が隣接する行を結合し、スキャン回数を減らします。--mergelines
と--delrem
がコメントと空行を除去します。--delempty
はコードブロックを再配置し、参照先の番号(リテラルのみ)を更新します。--move
「GOTO‑with‑POKE」トリックは、システム変数 PROG (23635) と NEWPPC/NSPPC (23618/23620) を使ってインタプリタの検索開始点を設定し、NXTLIN (23637) も同様に利用できます。これらの手法により、ジャンプ時間が短縮され CPU 負荷が低減し、実際の Spectrum ハードウェア上で BASIC プログラムを高速化できるため、レトロゲーム愛好家や保存プロジェクトにも恩恵があります。
本文
「ZX Spectrum の BASIC 純粋プログラムの(非)効率」シリーズ第1回目
I. ライン番号について
II. 変数について
III. 式について
IV. 機能と時間計測
V. キャラクタベース画面操作
ZX Spectrum の ROM に組み込まれている Sinclair BASIC インタプリタは、多くの点でソフトウェアの傑作と言えるでしょう。特にアセンブリ言語で書かれた実装が優秀です。本シリーズでは、時間実行性能だけでなくメモリ使用量も最小限に抑えるために、BASIC で書くプログラムの重要ポイントをまとめます。
今回取り上げるのは 「ライン」 です。数値化する必要があることは昔からずっと変わりませんが、インタプリタ自体の効率も無視できません。まずは、このマシンにおけるプログラム行の制限を整理しましょう。
-
番号と範囲
- ライン番号は 2 バイト(ビッグエンディアン)で保存されます。0~65535 まで使えるようになっていますが、実際にはエディタでは 1〜9999 のみ許可されています。POKE で 0 や 9999 を超える番号を設定可能ですが、
/GO TO
は 32767 を超えると失敗します。RESTORE
が 15871 を超える行にジャンプすると呼び出しスタックが壊れます。GO SUB- インタプリタは 65534 番を「編集バッファ実行中」を示すフラグとして使います。
- 9999 を越える番号の行を
すると正しく表示されず、手動で編集すると 4 桁に切り詰められます。LIST
- ライン番号は 2 バイト(ビッグエンディアン)で保存されます。0~65535 まで使えるようになっていますが、実際にはエディタでは 1〜9999 のみ許可されています。POKE で 0 や 9999 を超える番号を設定可能ですが、
-
行長
- 行長はライン番号の後に 2 バイト(リトルエンディアン)で格納され、番号や長さ自体は含まれません。
- 最大 65 535 バイトまで許容し、最小 6 バイト(2 バイト番号 + 2 バイト長 + 1 バイト終端 + 1 空白)。
- 実行時に 127 文以上を一行で書くと実行が中断されます(実際の上限)。
インタプリタの線形検索
BASIC は入力時に前処理され、キーワードはトークン化されますが、各行のメモリアドレス表は作られません。したがって「ある行を探す」操作は O(n) です。
/GO TO
/GO SUB
/NEXT
は線形に検索し、追加行数毎に約 71 µs を消費します。FN- 行番号がプログラム先頭にあるほど実行速度は速くなります。
コードブロックを移動させるには ZX‑Basicus の
--move オプションで自動再番号付けと参照更新が可能です。
速化テクニック
-
ループ・サブルーチンは先頭へ配置
は開始近くを指すようにし、同様にRETURN
も。GO SUB
-
パラメータ付きジャンプを避ける
- 定数リテラル(例:
、GOTO 100
)の方が速いです。GO SUB 2000
- 定数リテラル(例:
-
行長を最大化
- 行は可能な限り長くし、127 文までを一行にまとめます。
で連続行を結合できます。--mergelines
- 行は可能な限り長くし、127 文までを一行にまとめます。
-
コメント・データ削除
と空行は自動的に削除/末尾へ移動させるオプションREM
、--delrem
を利用。--delempty
-
「GOTO + POKE」テクニック
- システム変数
(23635、2 バイト)を先頭行アドレスに書き換えると、インタプリタはそれ以降の行だけを検索します。必要ならPROG
(23637)の値も POKE で変更可能です。NXTLIN
- システム変数
検索対象となる命令
| 命令 | コメント |
|---|---|
| GO TO | |
| GO SUB | |
| FN | DEF FN 行を検索 |
| RETURN | スタックに保存された行へ戻る |
| NEXT | 対応する FOR へジャンプ |
| RESTORE | |
| RUN | |
| LIST | |
| LLIST |
要点まとめ
- ループ・サブルーチン・ユーザー関数は先頭に配置。
- 長い行はメモリ節約に貢献し、短い行は内部検索を遅くする。
- インタプリタはアドレス表を持たず線形探索です(O(n))。
- ZX‑Basicus の
、--profile
、--move
、--mergelines
、--delrem
を活用。--delempty
目的 ― 実行箱全体のスコアを最大化 することです。