ラベル ビットスワップ の投稿を表示しています。 すべての投稿を表示
ラベル ビットスワップ の投稿を表示しています。 すべての投稿を表示

2011年6月14日火曜日

ビットスワップ

MOR命令4つで完全シャッフルをするのは超絶技巧だ. Schemeで書いたMORのシミュレータを使い, 実際にシャッフルしてみる.

MMIXのMORは$Yと$ZをMORして$Xに置くが, Schemeは関数なので, MORした値を返す.

まず行列の転置の関数がいる.

(define (tr m) ;行列mの転置
(map (lambda (i)
(map (lambda (b) (list-ref b i)) m))
(a2b 0 (length (car m)))))
;(tr '((1 1 1 1) (0 0 1 0) (0 1 0 0) (1 1 1 1)))
;=>((1 0 0 1) (1 0 1 1) (1 1 0 1) (1 0 0 1))

真理値に0,1を使うor関数もいる.

(define (or x y) (quotient (+ x y 2) 3))
;(or 0 0)=>0, (or 0 1)=>1, (or 1 0)=>1, (or 1 1)=>1

するとmorは

(define (mor y z)
(let ((zb (map (lambda (n) (n2bs n 8)) (n2bytes z 8)))
(yb (tr (map (lambda (n) (n2bs n 8)) (n2bytes y 8)))))
(bytes2n
(map bs2n
(map (lambda (z)
(map (lambda (y)
(fold-right or 0 (map * z y))) yb)) zb)))))

と書ける. (n2bs n w)は整数nをwビットのリストにする. n2bytesはバイトのリストにする.

あと, MUX関数も必要だ.

(define (mux m y z)
(let ((ms (n2bs m 64)) (ys (n2bs y 64)) (zs (n2bs z 64)))
(define (mx m y z)
(if (null? m) '()
(cons (if (= (car m) 1) (car y) (car z))
(mx (cdr m) (cdr y) (cdr z)))))
(bs2n (mx ms ys zs))))

この準備のもとで, 完全シャッフル関数は

(define (perfectshuffle z)
(let ((t (mor z #x8008400420021001)) (u 0))
(set! t (mor #x8020080240100401 t))
(set! u (mor t #x4080102004080102))
(set! u (mor #x4080102004080102 u))
(mux #xaa55aa55aa55aa55 t u)))

である. どのビットがどこへいったかを調べるには,

(define bin '(
#xffffffff00000000
#xffff0000ffff0000
#xff00ff00ff00ff00
#xf0f0f0f0f0f0f0f0
#xcccccccccccccccc
#xaaaaaaaaaaaaaaaa))

を次々とシャッフルし, 結果を二進法で出力する. それには

(define (bs x)
(substring (number->string (+ x (expt 2 64))2) 1 65))

を使う.

(map (lambda (n) (display (bs (perfectshuffle n))) (newline))
bin)

をやってみると,


が得られる. つまり, 左から元の位置は63, 31, 62, 30,...,32,0であったことが分かる.

2011年6月13日月曜日

ビットスワップ

TAOCP V4F0にはビット交換の技法がいろいろ書いてある. その大方の仕掛けはδスワップだが, TAOCPのベースのRISCマシン, MMIXにはMORとMXORという大形命令が存在し, それを巧みに利用する例題も多い.

これらの命令の説明はややこしく, なかなか覚えられないが, 今回のブログはその機能を覚えるためのものでもある.

MORはmultiple orのことで, 命令MOR $X, $Y, $Zは, 64ビットのレジスタ$Yと$Zのビットに演算を施し, レジスタ$Xに置くものである. TAOCP V1F1では,

m($X) ← m($Z)vxm($Y)

と説明する. レジスタ$Zと$Yの順が命令での順と逆なことに注意. この演算は, 64ビットレジスタをそれぞれ8ビットの行が縦に8行並んだ8 × 8の行列とみて, 2つの行列の乗算のような演算をするのである.

行列の乗算では, 積のi,j要素は, 被演算子の左の行列のi行と, 右の行列のj列の要素を次々と掛けて全体を足すのであった. それを2つの行列の間の演算+xで表わす.



今は要素は0か1である. それを次々と掛けて全体のor(v)を取るのである. MXOR命令は全体のxorを取る.

使い方の例として, TAOCPには
$Z = #0102040810204080
とすると, $Yのバイトの順が逆になるというのがある. つまり(abcdefgh)256が(hgfedcba)256になる. それを説明するのが, 次の図の上だ.



まず$Xの左上, X0,0は, $Zの0行目, 000...01と$Yの0列目のそれぞれを掛けるが, 1が最後にしかないので, ここに来るのはhのバイトの左端のビットである. その右隣りX0,1も, 同じ0行目と掛けるから, hのさっきのバイトの右隣りになる. 同様にした結果, $Xの0行目はhのバイトになる.

$Xの1行目は, $Zの1行目が000...10, つまり最後の1つ前にだけ1があるから, gの行が来る.

hの行が0行へ移った理由を再確認すると, Zの0行目の1が右端にあるからだ. つまり0行目へ置きたい$Yのバイトの位置を1にする. 赤矢印が0行目へ来るバイトの位置を示す.

そういう次第で, このパターンを使うと, $Yのバイトが逆転するのである.

下の図は左右逆転, つまりバイトの順は同じだが, バイトのビットを逆転する方法を示す. 上と同じで, $Xの0列目は, $Yの0列目の最後だけが1なので, hの各ビットが移動してきて, hになる. そして結局左からhgf...aと並ぶ.

ビット交換では, Yの0列の1の位置がXの0列に持ってきたいビットの位置である. (赤矢印)


これだけ分かると, V4F0にある完全シャッフルの問題(演習問題7.1.3-204)が解ける.

64ビットのレジスタzは

z = (x31...x1x0y31...y1y0)2

である. それを

w = (x31y31...x1y1x0y0)2

にしたい. ヒントがあって,

t = (x31x27x30x26x29x25x28x24y31y27y30y26y29y25y28y24...
    x7x3x6x2x5x1x4x0y7y3y6y2y5y1y4y0)2

u = (y27y31y26y30y25y29y24y28x27x31x26x30x25x29x24x28...
    y3y7y2y6y1y5y0y4x3x7x2x6x1x5x0x4)2

を中間結果とし, tとuを

MOR t,z,p; MOR t,q,t; MOR u,t,r; MOR u,r,u;

で作り,

PUT rM,m; MUX w,t,u;

とすると, 6命令で完全シャッフルが出来る. パターンp, q, r, mは何かという問題である.

このうちMUX命令は多重化マスクレジスタrMを見ながら, そのビットが1の所は$Yから, 0の所は$Zからデータを取る. PUT rM,mはパターンmをrMへ設定する命令だ. そうすると,

w31はx31にしたいからtから取る.
w30はy31にしたいからuから取る.
w29はx30にしたいからtから取る.
...
28まで行くと, 取るレジスタが交代する. 従って, 1010101001010101の繰り返しになるのだ. m = #aa55aa55aa55aa55


さて, zとtとの関係は, 次の図の上半分にある通り. バイトの交換とビットの交換をしている. 命令列の最初はMOR t,z,p;なので, $Zの方がパターンになり, バイトの交換だ. バイト位置の0,1,2,3,4,5,6,7に持ってきたいのは, それぞれ0,4,1,5,2,6,3,7だから, 下に示す行列のようになり, p = #8008400420021001でよかろう.



次の命令はパターンが$Yにあるから, ビット交換である. ビットの取り先は同じ関係だが, 縦と横が違うから転置のパターンにしなければならない.



q = #8020080240100401.

最後にtからuへの変換も, バイト交換とビット交換になっている. これはどちらも隣り同士の交換なので, パターンは同じである.

r = #4080102004080102.

TAOCPの演習問題には解答があるから覗いてみると, 正解であった.

2010年4月2日金曜日

ビットスワップ

2kswapを使い, 64ビットj(j5j4j3j2j1j0)2をjπ=(j0j1j2j3j4j5)2に置換したいというfftのビット変換の解も見事であるが, TAOCPの演習問題7.1.3-53にはもっと驚くことが書いてある.

jπ=(j0j1j2j3j4j5)2をさらに一般的にし, jπ=(j(d-1)ψ...jj)2と書く. ここで{0,1,...,d-1}の置換ψがt個の循環を持ち, d-t個の交換(u1v1)...(ud-tvd-t)について(ただしvi>uiとする), この順の積がψになるなら, この順に&mud,v&¬&muuのマスクで(2v-2u)swapをすればビットスワップが出来るというのだ.

完全シャッフルでは 5ψ=0, 4ψ=5, 3ψ=4, 2ψ=3, 1ψ=2, 0ψ=1だから(0 5 4 3 2 1) -> (5 4 3 2 1 0)には0→1→2→3→4→5→0と1個の循環しかないので, (0 1) (0 2) (0 3) (0 4) (0 5)の交換が必要である. 一方fftでは, 5→0→5, 4→1→4, 3→2→3の3個の循環があり, (u v) = (0 5) (1 4) (2 3)でビットスワップが出来ることになる.

それっというので, 早速前回のスワップをやってみる.
δは25-20=31, 24-21=14, 23-22=4で, マスクは

0000000000000000000000000000000010101010101010101010101010101010

0000000000000000110011001100110000000000000000001100110011001100

0000000011110000000000001111000000000000111100000000000011110000

である.

前回のような図も直ぐ書ける.



前回と殆んど同じ推論が出来る.

31swapでは,
右端の"/"を見るとj0=0, j5=0の時, j0は変らず, j5=0も変らず.
左端の"A"を見るとj0=1, j5=1の時, j0は変らず,j5=0も変らず.
右端の2番目"+"を見るとj0=1, j5=0の時, j0は変る, j5=0も変る.
左端の2番目"B"を見るとj0=0, j5=1の時, j0は変る, j5=0も変る.

これを表にしてみると:



なんのことはない, j0'=j5; j5'=j0であった. つまりビットj0とj5が交換できた. 後は推して知るべし. ビットは左右反転できたのである.

面白いなぁ.

2010年4月1日木曜日

ビットスワップ

3月12日のブログ, ビットスワップの続きである.

2kswapを使い, 64ビットの各ビットj(j5j4j3j2j1j0)2をjπ=(j0j1j2j3j4j5)2で置換したい. そのためのマスクθkを見つけよという問題である(演習問題7.1.3-52).

始めての事ゆえ, 見当もつかず, これもTAOCPの解答を見てみると,

d)0<=k<=5について θk6,k5-k; 0<=k<=2についてθ'kk; θ'3=θ'4=0.

とある. なぜか. 解明したい.

これだけ眺めていても埒があかないので, 64の各ビットがそれぞれのδswapでどこへ移動するかを描いてみた. 幾何の証明の時と同じで出来るだけ正確な図を描くのが肝心である. 幸いとPostScriptがあれば, 一発だ.



この図は
http://www.iijlab.net/~ew/images/bitswapchart.gif

にあるので, これが小さければ, 直接それを見た方がよいかも知れぬ.

δ=1,2,4,8,16,32についての移動パターンは規則的である. 置換πも規則的なので, そうなっても不思議はない.

少し調べてみると, 1swapでは
0****00****1 になり,
0****10****0 になり,
1***** は不変
であることが分かる. *はドントケアを表す.

この伝で他のswapも見たところ

32swap c****t
16swap *c**t*
8swap **ct**
4swap **tc**
2swap *t**c*
1swap t****c
であることが判明した. この読み方で, *はドントケア, tはテストビット, cはチェンジビットで, tが1なら不変, tが0ならcの0と1を変えるのである.

図も規則的だが, ビット変更も規則的である.

さてこれで j5j4j3j2j1j0が j0j1j2j3j4j5になるであろうか.

swapは図のようにδ=1,2,4,8,16,32,4,2,1の順に行う.

1swapではj5=0ならj0は反転し, j5=0ならj0は反転しないから, j'0←j0⊕j5⊕1 と書ける. (変化後のビットにはダッシュ(','')をつけて区別しよう. このように場合を区別するのに, ifを使わず, ⊕などで単一の式で表すのが味噌である.)

同様に, 続く2swap,4swapでj'1←j1⊕j4⊕1; j'2←j2⊕j3⊕1 となる.

次の8swapでも同様にj3が変化するが, これは新しいj'2がtビットなので,
j'3←j3⊕j'2⊕1=j3⊕(j2⊕j3⊕1)⊕1=j2.

同様にして16swapでj'4←j1; 32swapでj'5←j0 となる.

次の4swapではj'2=j2⊕j3⊕1 が新しいj'3つまりj2に従って変化する.
j''2←j'2⊕j'3⊕1=(j2⊕j3⊕1)⊕j2⊕1=j3.

同様にして2swapでj''1←j4; 1swapでj''0←j5 となり, 無事にビットが反転された. QED. めでたしめでたし.

2010年3月12日金曜日

ビットスワップ

Knuth先生のTAOCPは多くのことを盛り込みたいが故に, 説明は最小限であって, 手を動かし, プログラムを書き, 考えないとなんのこっちゃである. 7.1.3項演習問題52, 53もその類いである.

まずδswap. 下の図のxはビット列で, そのブロックAとC, BとDを交換し, x''としたい. 交換するブロックの長さはそれぞれ等しく, ブロック間の距離δはすべて等しい. その方法は, まずxを右にδ桁シフトしxと排他和をとり, 右側のブロックの形111...1でマスクし, yとする. ABA⊕Bのこと. xとyの排他和をx'とする. yを左にδ桁シフトしx'と排他和をとりx''とする. これをδswapという.



TAOCPではこの話題の前に, ビット列のi番目とj番目を交換したい. 本書の解を見る前に自分の方法を考えよとある. 私の考えはこれと同じであった.

この方法で64ビットのビット列を反転するには, 左右32ビットずつをそれぞれ反転し, 32ビットのブロックを交換する. それには032132のマスクがいる(0が32個並び1が32個並ぶパターンをこう書く). となり同士のビット交換には, ...010101のマスクがいる.
2kビットの0と2kビットの1で出来た(つまり...02k12k)長さ2dビットのマスクをμd,kと書く. 上の32ビットごとのマスクはμ6,5であり, ...010101はμ6,0である. ...010101は...111111を3で割ればよい. ...00110011は...11111111を5で割る. つまりμd,k=22d-1/22k+1である. これはパラメトロン計算機の頃からの常識である. この応用で1/3は二進法では0.010101..., 十進法の0.1は二進法では0.0001100110011...なのが分かる.

さて64ビットのビット列の任意の置換は, 適切なマスクθk, θ'kを用い,
k=0,1,...,5について x←θkによるxの2kswap.
k=4,3,...,0について x←θ'kによるxの2kswap.
で出来ると書いてある. 64ビットの反転はこの前半だけでよい.

この応用が演習問題52である. 次の各場合の置換のためのθk, θ'kを求めるのだ. もとの各ビット位置をj=(j5j4...j0)2とする.

a)完全シャッフル
jπ=(j0j5j4...j1)2
b)8×8ビット行列の転置
jπ=(j2j1j0j5j4j3)2
c)4×16ビット行列の転置
jπ=(j1j0j5j4j3j2)2
d)FFT(Fast Fourier Transform)に出てくるパターン
jπ=(j0j1j2j3j4j5)2

例を示すのに64ビットに名前をつける.RFC1341のBase64 Alphabetを用い

とする.左端Aの位置が111111, 右端/の位置が000000, 中央fが100000, gが011111である. 従ってa)の場合はgのj5の0が右端へ移動し111110に変わり,62になるからAの右に来る.そう考えると

にしたいのだ.

今は理解が先だから,まず解答を見てみる.

a) 0<=k<5についてθk6,k&μ6,5, θ'k6,k&(μ6,k+1⊕μ6,k-1);
θ54-1=0
と書いてある.

ではやってみよう.64文字はブログの画面では長すぎるので, 32文字のところで分割する. 各マスクの右はシフト数である.

一体どうなったか. まず32ビットシフトの直前までは, 右半分32ビットの反転である. これは簡単. そこで左, 右の32ビットの落ち着き先をみると, Aは63, fは1, /は0, gは62なので, 63-1(奇数のみ) 0-62(偶数のみ)である. 63-1 0-62 と書く. それぞれのブロックを中央で分けると, 落ち着き先は
63-33 31-1 0-30 32-62 [3 2 1 0]. 右の[と]の中はブロック番号. ブロック2と0を交換すると
63-31 32-62 0-30 31-1 になる. クイックソートと同じで, 今後中央を越えての移動はない.
またそれぞれを半分にする.
63-49 47-31 32-46 48-62 0-14 16-30 31-17 15-1 [7 6 5 4 3 2 1 0]
ブロック6と4, 3と1を交換. 左半分は右半分のほとんど鏡像だから, 右半分に注目.
31-17 16-30 0-14 15-1 になる. 半分にする.
31-25 23-17 16-22 24-30 0-6 8-14 15-9 7-1 [7 6 5 4 3 2 1 0]
6と4, 3と1を交換. 右半分に注目.
15-9 8-14 0-6 7-1
半分にする.
15-13 11-9 8-10 12-14 0-2 4-6 7-5 3-1 [7 6 5 4 3 2 1 0]
交換. 右半分を見ると
7-5 4-6 0-2 3-1
で, 文字では cd98/+ef. 次はdと8, /とeを交換 (2)
7 6 4 5 3 2 0 1 になる. c89de+/f
dと9, /とfを交換 (1) 7 6 5 4 3 2 1 0 になった.

とまぁこういうことをやったのである.

他の場合については別の機会に.