おい! あなたは%country%にいるようですが、%language%サイトを使用しますか?
Switch to English site
Skip to main content

自作プログラム言語「Forth」のベンチマークから見るMPUとMCUの今と昔

FIGnition: an Arduino-format single-board computer, based on the ATmega328 MCU
FIGINITIONボード:ATmega328 MCUをベースとし、FORTHでプログラムされたArduino形式のシングルボードコンピューター。モニターと電源を接続し動作させます。組み込み機器の開発者をターゲットにしているが、教育用としても優れたツールになっています。

数年前に、ヴィンテージコンピューター言語であるFORTHを、現代のマイクロコントローラであるMicrochip社のdsPIC33向けに作成するプロジェクトを始めました。これは1982年に当時Sinclair ZX81やSpectrumに搭載されていた Zilog Z80マイクロプロセッサ用に書いたLUT-Forthが出発点でした。一見すると、古い技術の8ビットマイクロプロセッサ(MPU)と最新の16ビットマイクロコントローラ(MCU)を比較するのは、全く異なるものを比較するかのように思えます。勿論、MCUには高性能なタイマーやカウンタ、シリアルバスコントローラなどの周辺機器ハードウェアがチップ上に搭載されています。こうした機能と統合型不揮発性のプログラムメモリやデータSRAMが組み合わさる事で、基本的なMPUがMCUへと変身したのです。最近のMPUにはWindowsやLinuxなどのOSを搭載した汎用コンピュータでの使用を考慮して、MMU(Memory Management Unit)GPU(Graphics Processor Unit)などのハードウェアが多く搭載されています。

私が開発したForthコンパイラ/インタプリタはいずれも組み込み用アプリケーション向けのコードを生成するように設計されています。1982年当時、Z80はどこにでもあるもので最初の「家庭用」コンピュータや、IBMのPCが登場する前には8インチのフロッピーディスクドライブを備えたデスクトップ/ラボマシンに搭載されていました。このオペレーティングシステムはCP/Mと呼ばれていました。マイクロコントローラチップは、Intel80551を除けば数が少なく埋め込み型プロジェクトにはメモリー、シリアルやパラレルのI/Oインターフェースチップ、アナログ・デジタル・コンバータ(ADCやDAC)などの周辺機器が必要でした。 

なぜForthなのか?

Forthというプログラミング言語が生まれた理由は以下のような理由からです:

  • FORTRAN、ALGOL、COBOLなどのメインフレーム用高級言語コンパイラは初期のMPUチップの最大64kBのメモリスペースには収まらないコードを生成していました。コンパイラはマイクロコンピュータで実行するには大きすぎて、プログラムを実行可能なマシンコードにコンパイルするにはメインフレームやミニコンピュータが必要でした。

  • BASICインタプリタはユーザーのソースプログラムと併せて何とか利用可能なメモリスペースに収め、オペレーティングシステムを必要としませんでした。しかし処理速度が非常に遅く、教育用にのみにしか向いていませんでした。初期のホームコンピュータがBASICを実行していたのもそうした理由からです!

  • ハードウェア制御や組み込み用途では高速な動作(オペレーション)が求められていました。

Forthの実装にはインタプリタとコンパイラの両方が含まれていますが、それでいてプログラム領域はわずか数キロバイトしか必要としません。BASICと同様にオペレーティングシステムを必要としません。またBASICとは異なるのは、ソースコードをインタプリタとは独立して実行するリンクリストに変換するコンパイラを持っています。その結果、非常に高速になります。Forthが速いのは構造がシンプルである事とインタプリタが解釈・コンパイル・実行するソースコードが人間ではなくコンピュータの脳に適した構造になっているからでもあります。それはソースコードの各行における演算子とオペランドの順序からよくわかります。

演算子とオペランドを正しい順序で並べる

一連の数値演算を記録する伝統的な方法は、変数と呼ばれる実際の数値を表すプレースホルダーを使って、方程式の形で記録することです。この方法を使うと、2つの数字の足し算の式は次のような形で書きます:

A = B + C で、AにはBとCの内容を足した結果が割り当てられます。

これは数学の表現と言えます、結果Aと同様にオペランド(B及びC)は変数であるため変数名の代わりに値を代入すると算術になります。また、演算子(+)が2つのオペランドの間に置かれていますのでインフィクス(infix)表記の例でもあります。BASICやPythonなどのプログラミング言語はこの表記法を採用しています。

対してほとんどすべてのポケット電卓が変数ではなく数字を使用しています。例えば

6 x 4 = 「=」キーを押すと演算(x)が実行され、その結果がディスプレイに割り当てられます。

インフィクス表記は人間の脳にとってはわかりやすい構造です。なぜならこの「イコール」記号のおかげで何が起こっているのかを簡単に追う事ができるからです。問題は、コンピュータは人間にやさしいこのインフィクス表記のコマンドラインを「理解できる」構文に変換するのに貴重な時間を費やしてしまう事です。演算子とオペランドの順序を入れ替えることでこのような不要なオーバーヘッドを取り除くことができます:

  • 「イコール」記号は不要

  • 演算を正しい順序で実行するための括弧も不要

そこで最初に前置(prefix)または「ポーランド記法(Polish)」で次のように演算子がオペランドの前に来るようにします。

B + C は + B C となり、+演算子は実行する前に2つのオペランドBとCを待つことを「知って」います。コンピュータ用語では、+とBはCが入力されるまで、後入れ後出し(LIFO)スタックに「プッシュされ」、その後演算が実行され結果がスタックの一番上の項目として残されます。等号は必要ありません。ではさらに複雑な例を示します。

(B – C) x D は x – B C D となります。演算子 x が対象の2つのオペランドを得るまで「スタック」され、– B Cが結果を出したときにのみ実行されることに注意してください。括弧は必要ありませんが、少し理解しがたい構文になってしまいました。

最後に、後置記法(postfix)または逆ポーランド記法(Reverse Polish)(RPN)です。これは驚くべきことに演算子の前にオペランドが来るのです。

(B – C) x D は B C – D x になり、 D B C – x にもなります。しかし今回は、演算子が入力されるまでオペランドはスタックにプッシュされ、スタック最後の2つのエントリに作用するようになっています。つまり、B - Cが最初に実行され、その結果がスタックの最上段に残され、続いて(B - Cの結果)×Dが実行されます。後置記法(ポストフィックス(Postfix))が中置や前置記法よりも優れているのは、演算子をスタックに一時的に保存する必要がないからです。

要するに、プロセッサがより簡単に「理解する」プログラミング構造を使用することに費やされる追加努力は、速度と効率の面で影響が出るという事です。言い換えれば、データと命令を正しい順序で与えれば一時的な記憶で時間を無駄にすることはありません。それがRPNの役割なのです。

Forthの比較

1980年代初頭には、現存するほぼすべてのマイクロプロセッサとそのバリエーションに対応するバージョンが非公式に書かれていました。今でも、FORTHの信奉者たちは、ARM Cortex-Mプロセッサコアを搭載したデバイスを含む、最新のデバイス用のコードを大量に作成しています。1982年には基本的なForth命令の実行速度をテストするための「ベンチマーク」プログラムが考案されました。基本的なForth命令の実行速度をテストするための16個の短いコードで、それぞれが10万回まで繰り返されるループに組み込まれており、その実行時間をストップウォッチで測り性能評価をします。これらのベンチマーク用のForthソースコードは以下の「ダウンロード」から入手できます。FORTHdsPICのベンチマークは、dsPIC33に搭載されている割り込み駆動の32ビットタイマーを使用することでより簡単に、そしてより正確に行えます。最新のベンチマークセットのForthコードはプロジェクトページからダウンロードできます。

ベンチマーク結果

各テストは、ストップウォッチで時間を測るため1万回繰り返されます
マグニファイアオーバーヘッドをBM2からBM16の結果から差し引いています。

ベンチマーク テスト

LUT-FORTH

FORTHdsPIC

スピード係数

x

BM1 マグニファイア 0.70 0.006 117
BM2

DO-LOOP

6.25 0.042 148
BM3 リテラル 9.25 0.068 136
BM4 変数 9.50 0.108 88
BM5 変数-保存 15.75 0.161 97
BM6 変数-フェッチ 12.75 0.137 93
BM7

定数-フェッチ

9.25 0.108 87
BM8 DUP 12.25 0.095 129
BM9 インクリメント 12.25 0.094 130
BM10 テスト > 16.25 0.128 127
BM11 テスト < 16.25 0.128 127
BM12 BEGIN-WHILE-RPT 17.25 0.164 105
BM13 BEGIN-UNTIL 15.25 0.147 103
BM14 リンク検索 7.2 0.040 180
BM15 SP 算術 9.25 0.027 342
BM16 DP 算術 10.75 0.028 384

結果を見ると、70MHzのdsPIC33Eが4MHzのZ80Aよりも遥かに速いことを示しています。この結果は特段驚くことではありませんね。しかし、実は以下のような意外な要因もこの結果に影響しています:

  • Z80は8ビットのマシンです。しかし、これは6つの8ビットワーキングレジスタを3つの16ビットレジスタペアとして取り扱える命令をいくつか持っています。

  • Z80チップはIntel8080の「強化版」であり、より多くのレジスタと大幅に強化された命令セットを備えています。LUT-Forthは8080の命令とレジスタセットのみを使用しているので、Z80の機能をフルに使っていれば、おそらくもっと高速で動作していたでしょう。

  • dsPIC33のほとんどの命令は、70MHzの1クロックサイクルで実行されます(BRAnchesとCALLを除く)。しかしZ80の命令には、複数の4MHzクロックサイクルを必要とします。

  • dsPIC33は、単精度と倍精度の演算タイミングに関しては別格です。これは、dsPIC33にはシングルサイクルで符号付き及び符号なしの混合精度の乗算命令を持つ乗算・除算ハードウェアを搭載しているからです。

  • dsPIC33は非常にスタック指向の強いプロセッサです。Forthの実装には、複数のハードウェアスタックと、大半の命令がスタックに直接アクセスできるアドレッシングモードが有効でメリットを享受できます。

まとめ

Forthは高速でメモリー使用量が少ないため、組み込みアプリケーションを実行する小型マイクロプロセッサに適していると考えられてきました。同時に、RPNの使用は「難しい」と思われているため、多くの人が敬遠しています。しかしForthのコードは信頼性が高く、バグを含む可能性も少ないです。これはプログラマーがコードの生成に細心の注意を払っているからだと思われます。細心の注意を払えるように設計されている点としては、新しい単語の定義をコンパイルする前にインタプリタモードでテストできることも効果的です。勿論、この機能以外にも問題を削減する工夫がされています。コードの信頼性とスピードはNASAやESAのロボット宇宙船 はForthでプログラムされている理由の一つであり、放射線耐性を備えた特別なマイクロコントローラHarris/Intersil RTX2010上で動作していることが多いです。

Z80 MPUは、Z84C00xx (169-8229) 、Z80180xx (177-7104) 、およびeZ80F91 (165-9325) の形式があります。

何かをやろうとしていて、いいアイデアを思いつかない時はぜひ、Twitterで私の投稿をフォローしてください。新しい電子機器や関連技術に関する興味深い記事にリンクしロボット、宇宙探索、その他の問題について見つけた投稿をリツイートしています。

Engineer, PhD, lecturer, freelance technical writer, blogger & tweeter interested in robots, AI, planetary explorers and all things electronic. STEM ambassador. Designed, built and programmed my first microcomputer in 1976. Still learning, still building, still coding today.
DesignSpark Electrical Logolinkedin