【GMC-4】命令の実行速度を調べる

学研の4ビットマイコンGMC-4の命令の実行速度が気になったので調べてみた。
ついでに互換機やシミュレータについても調べてみた。

# 測定対象

今回は、以下の5種類について実験を行った。

* FX-マイコン (学研電子ブロックFX-SYSTEM FX-マイコンR-165)
* GMC-4 (大人の科学マガジン Vol.24 のふろく)
* [ORANGE-4](http://www.picosoft.co.jp/ORANGE-4/index.html)
* [FX-マイコン シミュレータ](https://www.vector.co.jp/soft/win95/util/se289783.html)
* [GMC-4 シミュレータ](http://dansan.air-nifty.com/blog/gmc4-simulator.html)

シミュレータの実行環境は以下である。

Windows 10 Home 21H2 (64ビット)
Intel(R) Core(TM) i7-9750H CPU @2.60GHz 2.59GHz
RAM 16.0GB

# 測定方法

以下のプログラムにより測定を行った。
なお、このプログラムは[MikeAssembler](https://mikecat.github.io/mike_assembler/)によるアセンブルが可能である。

```
target gmc4

	TIA 1
	TIY 3
INIT_ONE_LOOP:
	AM
	AIY -1
	JUMP INIT_ONE_LOOP
INPUT_WAIT:
	KA
	JUMP INPUT_WAIT
	CY
	TIA 0
	JUMP INIT_START
INIT_LOOP:
	AM
INIT_START:
	AIY -1
	JUMP INIT_LOOP

	CAL SHTS
MAIN_LOOP:
	; 実行時間を計る命令を追加する場合、ここに入れる
	; ループのカウントを行う
	TIY 0
	MA
	AIA -1
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 1
	MA
	AIA -1
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 2
	MA
	AIA -1
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 3
	MA
	AIA -1
	AM
	CIA 0
	JUMP MAIN_LOOP

	CAL SHTS
END:
	JUMP END
```

以下がこのプログラムの機械語表現である。

```
  | 0 1 2 3  4 5 6 7  8 9 A B  C D E F
--+-----------------------------------
0 | 8 1 A 3  4 B F F  0 4 0 F  0 A 3 8
1 | 0 F 1 5  4 B F F  1 4 E 9  A 0 5 9
2 | F 4 C 0  F 1 C A  1 5 9 F  4 C 0 F
3 | 1 C A 2  5 9 F 4  C 0 F 1  C A 3 5
4 | 9 F 4 C  0 F 1 C  E 9 F 4  A
```

まず、負荷量の設定を行う。
今回のプログラムでは、最大4重のループを用いる。
ループ回数を決めるカウンタをとりあえず全て1 (1回実行する) に初期化し、
キー入力された個数のカウンタを0 (16回実行する) に書き換える。
入力は0～4が有効である。

キー入力を行うと、ループ処理が実行される。
各ループは以下のブロックで表現され、同じ構造のブロックが4個連なっている。

```
	TIY (0-3)       ; ループカウンタを選択する
	MA              ; ループカウンタをAレジスタに読み出す
	AIA -1          ; ループカウンタをデクリメントする
	AM              ; ループカウンタを保存する
	CIA 0           ; ループカウンタが0かをチェックする
	JUMP MAIN_LOOP  ; ループカウンタが0でなければ、ループを続行する
```

今回はこのブロックを何回実行するかを計算し、それに基づいて1秒あたりの命令実行数を計算することにした。
1命令の実行時間は命令の種類や実行結果(ジャンプするかどうかなど)によって変わることが予想されるが、簡単のため今回は無視することにした。

さらに、ループ処理の前後で `CAL SHTS` 命令により音を鳴らすようにした。
これらの音を録音し、最初の音が鳴り始めてから2番目の音が鳴り始めるまでの時間を調べることにより、命令の実行時間を計測することにした。
この時間には、ループ処理にかかる時間に加え、`CAL SHTS` 命令1回分の実行時間も含まれる。
そこで、ループの回数を変えて時間を測定し、連立方程式を解くことで、`CAL SHTS`命令の実行時間とループ処理の実行時間に分けることにした。

* 1種類目におけるブロックの実行回数を $n_1$
* 1種類目の実行時間を $t_1$
* 2種類目におけるブロックの実行回数を $n_2$
* 2種類目の実行時間を $t_2$
* `CAL SHTS` 命令の実行時間を $s$
* ブロック1回あたりの実行時間を $b$

とおくと $s + n_1 b = t_1, s + n_2 b = t_2$ と表せるので、これを解けば $s, b$ の値が求まる。

# ブロックの実行回数

## 負荷設定0

各ブロックを1回ずつ実行するので、実行回数は4回である。

## 負荷設定1

* 最初のブロックを16回実行する
* 2～4番目のブロックを1回ずつ実行する

よって、実行回数は19回である。

## 負荷設定2

* 2番目のブロックのループにより、以下を16回実行する
  * 最初のブロックを16回実行する
  * 2番目のブロックを1回実行する
* 3番目・4番目のブロックを1回ずつ実行する

よって、実行回数は$(16+1)\times 16 + 1 + 1 = 274$回である。

## 負荷設定3

* 3番目のブロックのループにより、以下を16回実行する
  * 2番目のブロックのループにより、以下を16回実行する
    * 最初のブロックを16回実行する
    * 2番目のブロックを1回実行する
  * 3番目のブロックを1回実行する
* 4番目のブロックを1回実行する

よって、実行回数は$((16+1)\times 16 + 1)\times 16 + 1 = 4369$回である。

## 負荷設定4

* 4番目のブロックのループにより、以下を16回実行する
  * 3番目のブロックのループにより、以下を16回実行する
    * 2番目のブロックのループにより、以下を16回実行する
      * 最初のブロックを16回実行する
      * 2番目のブロックを1回実行する
    * 3番目のブロックを1回実行する
  * 4番目のブロックを1回実行する

よって、実行回数は$(((16+1)\times 16 + 1)\times 16 + 1)\times 16 = 69904$回である。

# 測定結果

実行時間の測定結果は、以下のようになった。
なお、本来は複数回測定して平均を取るべきだろうが、今回は簡単のため1回のみ測定した。

|実行環境|負荷設定0|負荷設定1|負荷設定2|負荷設定3|負荷設定4|
|---|--:|--:|--:|--:|--:|
|FX-マイコン|-|0.492秒|2.826秒|-|-|
|GMC-4|-|-|0.615秒|5.236秒|-|
|ORANGE-4|-|-|-|0.514秒|2.970秒|
|FX-マイコン シミュレータ|-|2.315秒|27.822秒|-|-|
|GMC-4 シミュレータ|-|-|-|0.320秒|0.340秒|

この測定結果から実行時間を求め、1秒あたりの命令実行数に換算すると、以下のようになった。
1ブロックが6命令からなることを利用している。

|実行環境|`CAL SHTS`命令の実行時間|1ブロックあたりの実行時間|1秒あたりの命令実行数|
|---|--:|--:|--:|
|FX-マイコン|0.318秒|0.00915秒|656命令|
|GMC-4|0.306秒|0.00113秒|5317命令|
|ORANGE-4|0.350秒|0.0000375秒|160102命令|
|FX-マイコン シミュレータ|0.414秒|0.100秒|60命令|
|GMC-4 シミュレータ|0.319秒|0.000000305秒|19660500命令|

すなわち、

* GMC-4は1秒あたり約5k命令実行できる
* FX-マイコンはGMC-4の約8分の1の実行速度
* ORANGE-4はGMC-4の約30倍速い
* GMC-4 シミュレータはGMC-4の約3700倍速い
* FX-マイコン シミュレータはGMC-4の約90分の1の実行速度

という結果になった。

# 7セグメントLEDを点灯させての測定

[FX-マイコン](https://qiita.com/mikecat_mixc/items/cde539fe570dfebf18ea)は7セグメントLEDを点灯させると実行速度が落ちるようだったので、
先程のプログラムの最初の `TIA 1` の次に `AO` を挿入し、
7セグメントLEDを点灯させるようにしたプログラムで再度測定を行ってみた。
以下がこの変更を行ったプログラムの機械語表現である。

```
  | 0 1 2 3  4 5 6 7  8 9 A B  C D E F
--+-----------------------------------
0 | 8 1 1 A  3 4 B F  F 0 5 0  F 0 B 3
1 | 8 0 F 1  6 4 B F  F 1 5 E  9 A 0 5
2 | 9 F 4 C  0 F 1 D  A 1 5 9  F 4 C 0
3 | F 1 D A  2 5 9 F  4 C 0 F  1 D A 3
4 | 5 9 F 4  C 0 F 1  D E 9 F  4 B
```

同様に1回ずつ測定を行った結果、以下の測定結果が得られた。

|実行環境|負荷設定0|負荷設定1|負荷設定2|負荷設定3|負荷設定4|
|---|--:|--:|--:|--:|--:|
|FX-マイコン|-|0.565秒|3.845秒|-|-|
|GMC-4|-|-|0.614秒|5.237秒|-|
|ORANGE-4|-|-|-|0.471秒|3.080秒|
|FX-マイコン シミュレータ|-|2.306秒|27.908秒|-|-|
|GMC-4 シミュレータ|-|-|-|0.329秒|0.350秒|

この測定結果から実行時間を求め､1秒あたりの命令実行数に換算すると､以下のようになった｡
1ブロックが6命令からなることを利用している｡

|実行環境|`CAL SHTS`命令の実行時間|1ブロックあたりの実行時間|1秒あたりの命令実行数|実行速度(消灯時比)|
|---|--:|--:|--:|--:|
|FX-マイコン|0.321秒|0.0129秒|466命令|71.2%|
|GMC-4|0.305秒|0.00113秒|5315命令|100.0%|
|ORANGE-4|0.297秒|0.0000398秒|150713命令|94.1%|
|FX-マイコン シミュレータ|0.398秒|0.100秒|60命令|99.6%|
|GMC-4 シミュレータ|0.328秒|0.000000320秒|18724286命令|95.2%|

* FX-マイコンでは、約30%という顕著な速度の低下がみられた。
* GMC-4では、速度はほとんど変わらなかった。
* ORANGE-4では、5%程度の速度の低下がみられた。
* FX-マイコン シミュレータでは、速度はほとんど変わらなかった。
  * 「動作をできるだけ忠実に再現しました」と主張しているが、ここは再現できなかったようだ。(そもそも遅いし)
* GMC-4 シミュレータでは、5%程度の速度の低下がみられた。
  * 測定された時間が短いため、実行時の他のタスクの違いなど誤差の可能性も考えられる。

ORANGE-4における7セグメントLEDの点灯による実行速度の低下を確かめるため、
以下のプログラムでPORT1へ0/1の出力を繰り返し、オシロスコープ FNIRSI-1013D で周波数を測定した。

```
target orange4

	ldyi 1
	ldi 0
	ioctrl
loop:
	ldi 1
	out
	ldi 0
	out
	jmpf loop
```

以下がこのプログラムの機械語表現である。

```
  | 0 1 2 3  4 5 6 7  8 9 A B  C D E F
--+-----------------------------------
0 | A 1 8 0  F 7 0 8  1 F 7 1  8 0 F 7
1 | 1 F 0 7
```

以下が測定結果である。

![ORANGE-4 によるポートの上下 (7セグメントLED消灯)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/98efbd2d-26ee-9cfd-33c0-a2bcae86542f.png)



さらに、最初の `ldi 0` の次に `outn` 命令を追加し、
7セグメントLEDを点灯させる以下のプログラム(機械語表現)について、同様に測定した。

```
  | 0 1 2 3  4 5 6 7  8 9 A B  C D E F
--+-----------------------------------
0 | A 1 8 0  1 F 7 0  8 1 F 7  1 8 0 F
1 | 7 1 F 0  8
```

以下が測定結果である。

![ORANGE-4 によるポートの上下 (7セグメントLED点灯)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/eae20e49-765d-6567-46be-efb7f2f74cb2.png)

7セグメントLED消灯時は28.3kHzであるのに対し、7セグメントLED点灯時は27.1kHzとなっており、95.8%の実行速度となっている。
ORANGE-4では7セグメントLEDを点灯させると約5%速度が低下することを、別の形でも確かめることができた。

# GMC-4 シミュレータの追加検証

GMC-4 シミュレータは他と比べて非常に高速で動作し、負荷設定4でも1秒未満で実行できた。
そこで、プログラムの入力部分を削ることで容量を確保し、負荷設定5および負荷設定6相当で検証を行った。

以下が検証用のプログラムである。(GMC-4 シミュレータ上で直接アセンブルできる)

```
	; 7セグメントLEDを点灯させる際､コメントアウトを解除する
	;AO
	CAL SHTS
MAIN_LOOP:
	; ループのカウントを行う
	TIY 0
	MA
	AIA 0xF
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 1
	MA
	AIA 0xF
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 2
	MA
	AIA 0xF
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 3
	MA
	AIA 0xF
	AM
	CIA 0
	JUMP MAIN_LOOP
	TIY 4
	MA
	AIA 0xF
	AM
	CIA 0
	JUMP MAIN_LOOP
	; 負荷設定6にする際､コメントアウトを解除する
	;TIY 5
	;MA
	;AIA 0xF
	;AM
	;CIA 0
	;JUMP MAIN_LOOP

	CAL SHTS
END:
	JUMP END
	
	ORG 0x50
	DN 0, 0, 0, 0, 0, 0, 0, 0
```

1回ずつ測定を行った結果、以下の測定結果が得られた。

|実行環境|負荷設定5|負荷設定6|
|---|--:|--:|
|GMC-4 シミュレータ (7セグメントLED消灯)|0.680秒|6.180秒|
|GMC-4 シミュレータ (7セグメントLED点灯)|0.690秒|6.180秒|

また、負荷設定5の実行回数は、
「負荷設定4の処理と最後のブロック」を16回実行するので $(69904 + 1) \times 16 = 1118480$ 回
負荷設定6の実行回数は､
｢負荷設定5の処理と最後のブロック｣を16回実行するので $(1118480 + 1) \times 16 = 17895696$ 回
となる。

この測定結果から実行時間を求め､1秒あたりの命令実行数に換算すると､以下のようになった｡
1ブロックが6命令からなることを利用している｡

|実行環境|`CAL SHTS`命令の実行時間|1ブロックあたりの実行時間|1秒あたりの命令実行数|
|---|--:|--:|--:|
|GMC-4 シミュレータ<br>(7セグメントLED消灯)|0.313秒|0.000000328秒|18302417命令|
|GMC-4 シミュレータ<br>(7セグメントLED点灯)|0.324秒|0.000000327秒|18335755命令|

7セグメントLED点灯時の実行速度は7セグメントLED消灯時の100.2%となり、ほとんど差がないという結果になった。
負荷設定4までの実験と比べてある程度の実行時間を確保できており、より信頼できると考えられる。

# 結論

今回の実験では、各実行環境の実行速度は以下のようになった。
なお、実行にかかる時間は命令や条件によって変わる可能性がある。

|実行環境|実行速度<br>(7セグメントLED消灯時)|7セグメントLED点灯による<br>実行速度の低下|
|---|--:|--:|
|FX-マイコン|656命令/s|約30%|
|GMC-4|5.31k命令/s|見られず|
|ORANGE-4|160k命令/s|約5%|
|FX-マイコン シミュレータ|60.0命令/s|見られず|
|GMC-4 シミュレータ|18.3M命令/s|見られず|

参考になれば幸いである｡