index

2006年 12月
          1 2 3
  4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
  25 26 27 28 29 30 31
2007年 1月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
2007年 2月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28        

アレ

4レジスタ アーキテクチャ   ▽20070117a #日記

あたしの脳内の数値演算に使えるレジスタがどうやら 4つくらいしかない,ってお話.

むかーしむかし.まだ電車に乗るのに紙の切符が使われていた頃(今でもあるの?). 切符には発券番号が 4桁の数字で記録されているのが普通で, この番号って 9999 の次に達したらどうなるんだろうと思ったら 0000 にループらしい. それって同じ番号を別の人が同一日に取得する可能性があるわけで, 意味なくね? とか子供心に思ったわけだが, よく考えると券売機で切符を買うのにまず 10秒以上は余裕でかかるわけで, 近所の駅では 4台の券売機があったから番号ループにかかる時間はおよそ 7時間. まぁ普通にやってれば重なることはないなと安心したのだけど, もっとデカい駅なら 2~3時間で重複出ちゃうだろってことに思い至らなかったのは子供ですから.ええ.

で,この番号の数字 4桁を四則演算で結びつけて 10 にする,という遊びがあるっしょ. 「1 2 3 4」なら「1 + 2 + 3 + 4」,「2 5 8 0」なら「2 * 5 + 8 * 0」みたいな. こないだ渋滞でハマってた時に,ふと周囲のクルマのナンバプレートを見ていてそれを思い出し, ナンバ 4桁でその遊びをしていたのだけど, もうひとつの遊び方を思いついてやってみたのが, 「2桁ずつを暗算で掛け算する」という単なる暗算能力の修行.

というのもだ,まぁ大抵の日本人は同じだと思うんだけど, 1桁同士の掛け算ってのは演算結果テーブルを暗記していて,すぐに出来るのね. いわゆる「九九」ってやつだな. テーブルルックアップ方式は問答無用の 1ステップで答えが出るからとにかく演算が速い. プログラム高速化の常套手段だ. だけど 2桁の掛け算ってのは日本の学校では暗記するようには教わらないので, これをうまいこと計算しないといけない.

大抵の人は 10進数で計算するので,「* 10」の演算は例外的に速くできる. これは演算というよりビットシフトに近い.shift() と表記することにする.

あと足し算は 2桁同士でも AB + CD → ( B + D ) + shift( A + C + carry ) って書くと意味わかんねーが 要は筆算のように下の位を足して繰り上がりつつ上の位を足してって手順だな. これは整理すると,オペランド AB と AC を記憶しておく以外に, 演算中の値を確保しておくレジスタが 2つ必要になる. 1桁ずつの演算結果( B + D とか)用にひとつと,そこまでの演算途中の結果用にひとつ. carry 用のバッファが必要なタイプの人はそこにもレジスタビットが必要だな.

で,それ以外を全て地道に計算するとなると, AB * CD は以下のようになる.

 r1 ← A * C;
 r2 ← A * D;
 r3 ← B * C;
 r4 ← B * D;
 r5 ← shift( r1 ) + r2
 r6 ← shift( 10 * r3 ) + r4
 ans ← add( r5 + r6 )

かなり手数が多くて大変だ. shift() はともかく add() には上記の通りレジスタが 2つ余分に必要で, この方式の場合は合計で 8つのレジスタが必要ってことになる. 実際には r5 に代入した時点で r1 と r2 は破棄,r6 も同様, で同時に動くのは最後の add() の時点で r5 / r6 / rAdd1 / rAdd2 の 4つだけど, ここまできれいにレジスタリネーミングするのはそれはそれで大変.

なので,数値ごとに細かい最適化や演算省略を探すことになるわけだ. 例えば,片方の一の位が 0 だった場合.AB * C0 みたいな.

 r1 ← shift( A * C );
 r2 ← B * C;
 ans ← shift( r1 + r2 );

レジスタはこれだけで済むのだ.r1 + r2 は片方が shift() されているので, 実際に演算する有効桁数は 2桁同士ではなく 1桁の計算とほぼ変わらない.

では片方の一の位が 9 だった場合.AB * C9 みたいな.

 r1 ← shift( A * ( C + 1 ) );
 r2 ← B * ( C + 1 );
 r3 ← shift( r1 + r2 );
 ans ← sub( r3 - AB );

1~4桁の引き算が出てくるが,まっとうに演算するより遥かに手数が少なくて済む.

……と,まぁこんな風に,2桁同士の掛け算を暗算でやるための最適化パターンを探す, という遊びだったわけだが. 湾岸線がちょー混みだったので随分と長いことこれをやってて気づいたことが, 冒頭のレジスタ数制限ってわけだ.

正確には 4つしかないというより「4つ以上をアクティブに動作させるには負荷(集中力ともいう)がかかる」と. あと,あたしの脳は,レジスタリネーミングにはほぼレジスタひとつ分くらいのリソースが必要っぽい. つまり先に述べた「2桁の掛け算・まっとうに全部演算」は, あたしの脳ではかなり集中しないとできないってわけだ.

うーん,暗算とか得意な方ではないと思っていたが(だからこんな修行遊びを思いついたわけだし), ここまでとは…… やはりここはテーブルルックアップ方式を導入せねばならんかのう. 特に苦手な 6 と 7 が絡むあたりを.

……と思ってちょっと前に「二桁のかけ算 一九一九」を買ったんだけど, まだ読んでねーです.はい.なるべく早く読みます.はい.

index