ハードウェア的プログラミングのすすめ
はじめに
並列に動作する複数の演算ユニットを持ち、プロセッサ内部は高速にパイプライン動作する今日の高性能プロセッサでは、
- 分岐予測(や投機的実行)の失敗によるパイプラインストール
- レジスター・インターロック
- キャッシュ・ミス
- 低速な外部バスへのアクセス
を減らすことを考慮したプログラミングが、プロセッサの性能を引き出す重要なポイントになっています。 局所的にはコンパイラによるオプティマイズや命令のスケジューリングにより、ある程度解決されますが、大事なのは、アルゴリズムや構成として適したものになっていることです。
性能が重要ならば、if 文や for 文を多用した 文学的 プログラミングスタイルよりも、 論理・算術演算による ハードウェア的 プログラミングスタイルの方が適していると言えます。 演算ユニットが複数あるプロセッサでは並列に処理できる箇所を増やすことも大切です。
ハードウェア的プログラミング例
List1 に示すCプログラムは、タイマー割り込みで呼び出されるキーパッド読み込み関数です。
readkeypad() でデグリッチ処理を行い、keyevent() でキーパッド押し下げイベントの検出と処理を行っています。
#define KEYPORT ((int *)0x80000040)
static int
readkeypad()
{
static unsigned int shifter[4] = {0,0,0,0};
static unsigned int shiftp = 0;
static unsigned int q = 0;
volatile register unsigned int *keyport = KEYPORT;
shifter[shiftp++] = *keyport;
/* JK-FlipFlop */
q = (~q & (shifter[0] & shifter[1] & shifter[2] & shifter[3]))
|( q & ~(shifter[0] | shifter[1] | shifter[2] | shifter[3]));
shiftp &= 0x03; /* equivalent to shiftp mod 4 */
return(q); /* returns key state */
}
int
keyevent()
{
static unsigned int d = 0;
register unsigned int upedge;
register unsigned int priobit;
register unsigned int pressed;
register unsigned int q;
q = readkeypad();
priobit = 0;
pressed = upedge = q & ~d;
while(upedge ^= priobit){
/* Lower bit has higher priority */
priobit = (upedge & (~upedge + 1));
switch(priobit){
/* do key on process here */
default: break;
}
}
return(pressed); /* returns pressed key */
}
readkeypad() の要は、キー入力ポートから読み込んだ各 bit の値が連続して 4回 '1' であった場合にセットされ、
同様に連続して '0' であった場合にリセットされる、ある種の Digital Low-Pass-Filter 付き JK-Flip-Flop を構成することにより、
条件判断を一切用いていないところです(実際にはコンパイラにより条件分岐が1つ入る可能性があります)。
また、& や | の演算箇所では、変数間に依存関係が無いためにプロセッサによっては並列処理ができるようになっています。
keyevent() の要は、
ハードウェアから見ればほとんど起こる事の無いキー押し下げイベントを判断する為に全ビットを無駄にをスキャンすることなく、
List2 のように、算術演算を用いて '1' の立っている最も下位のビットを検出している点です。
priobit = (upedge & (~upedge + 1));
まとめ
アセンブリ言語全盛の 1970 年代には、このようなハードウェアを意識したコーディングは当たり前でしたが、 今また、シンプルな命令を大量に処理するのに長けた今日のプロセッサの性能を活かすために、 まるで ロジック回路を組むような「ハードウェア的」なプログラミングは如何でしょうか。