15. 乱数生成 -- Mersenne Twister --
前回のサンプルでは,/dev/urandom から取得した乱数を 使ってフレームバッファにランダムに点を描画しましたが,非常に遅いことに気がつきます. 今回は疑似乱数生成アルゴリズムとして Mersenne Twister の C による32ビット整数版 mt19937int.c をアセンブリに移植します.Mersenne Twisterは 周期が非常に長く (2^19937-1),高次元(623次元)で均等に分布し,生成速度が速いという特徴をもつ 非常に高性能な疑似乱数生成アルゴリズムです.
mt19937int.c をコンパイラに翻訳させてみます.
$ gcc -O2 -o mt19937int mt19937int.c $ ls -l mt19937int -rwxr-xr-x 1 jun users 13939 Mar 11 16:08 mt19937int $ ndisasm -b 32 mt19937int >mt19937int.lst
逆アセンブルしたリスト (mt19937int.lst) を読むと(void lsgenrand(seed_array) を除いて) 400バイト強のコードが生成されています.このままでは面白くないので 小さいコードに挑戦してみました. 速度が若干を犠牲になりましたが,256 バイトまで小さくなりました.コードは読みにくく なっています.興味のある方は C のオリジナル mt19937int.c と読み比べてください.
;--------------------------------------------------------------------- ; Mersenne Twister ; file : mt19937.inc ; Rewritten in Assembly by Jun Mizutani 2001/03/11. ; From original code in C by Takuji Nishimura(mt19937int.c). ;--------------------------------------------------------------------- ; This library is free software; you can redistribute it and/or ; modify it under the terms of the GNU Library General Public ; License as published by the Free Software Foundation; either ; version 2 of the License, or (at your option) any later ; version. ; This library is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ; See the GNU Library General Public License for more details. ; You should have received a copy of the GNU Library General ; Public License along with this library; if not, write to the ; Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA ; 02111-1307 USA ;-- The original C code contained the following notice: ; Copyright (C) 1997, 1999 Makoto Matsumoto and Takuji Nishimura. ; Any feedback is very welcome. For any question, comments, ; see https://www.math.keio.ac.jp/matumoto/emt.html or email ; matumoto@math.keio.ac.jp %assign N 624 %assign M 397 section .text ;--------------------------------------------------------------------- ; Initialize Mersenne Twister ; enter eax : seed ;--------------------------------------------------------------------- sgenrand: pusha mov esi, N mov ebx, 69069 mov ebp, 0xffff0000 xor edx, edx .loop: mov edi, eax ; eax : seed and edi, ebp ; 0xffff0000 imul eax, ebx ; seed * 69069 inc eax mov ecx, eax and ecx, ebp ; 0xffff0000 shr ecx, 16 or ecx, edi mov [mt+edx*4], ecx ; mt[ebx] imul eax, ebx ; 69069 inc eax ; seed inc edx cmp edx, esi jl .loop mov dword [mti], esi ; N:624 popa ret ;--------------------------------------------------------------------- ; Generate Random Number ; return eax : random number ;--------------------------------------------------------------------- genrand: pusha mov esi, mt mov edi, mti mov eax, [edi] mov ecx, N-1 cmp eax, ecx ; 623 jle .genrand2 mov ebp, mag01 shl ecx, 2 ; (N-1)*4 xor ebx, ebx .loop1: call .common xor eax, [esi+ebx+M*4] ; mt[kk+M] call .common2 cmp ebx, (N-M-1)*4 ; N-M-1 jle .loop1 cmp ebx, ecx ; (N-1)*4 jge .next .loop2: call .common xor eax, [esi+ebx+(M-N)*4] ; (M-N=-227) call .common2 cmp ebx, ecx ; (N-1)*4 jl .loop2 .next: mov edx, [esi+ecx] ; (N-1)*4 mov eax, [esi] call .common1 xor eax, [esi+(M-1)*4] call .common2 ; ebx = ecx xor eax, eax mov [edi], eax ; mti=0 .genrand2: mov eax, [edi] mov edx, [esi+eax*4] ; mt[mti] inc dword [edi] ; mti++ mov eax, edx shr eax, 11 xor edx, eax mov eax, edx shl eax, 7 and eax, 0x9d2c5680 ; TEMPERING_MASK_B xor edx, eax mov eax, edx shl eax, 15 and eax, 0xefc60000 ; TEMPERING_MASK_C xor edx, eax mov eax, edx shr eax, 18 xor edx, eax mov [esp+28], edx ; return eax popa ret .common: mov edx, [esi+ebx] mov eax, [esi+ebx+4] .common1: and edx, 0x80000000 and eax, 0x7fffffff or edx, eax mov eax, edx shr eax, 1 ret .common2: and edx, byte 1 xor eax, [ebp+edx*4] mov [esi+ebx], eax add ebx, byte 4 ret ;============================================================== section .data mag01 dd 0x00000000 dd 0x9908b0df mti dd N+1 ;============================================================== section .bss mt resd N
実際に乱数を生成して表示してみます.コードが小さくなっても間違った結果 ではシャレになりませんからオリジナルの出力結果 mt19937int.out と同じフォーマットで出力しています.
;--------------------------------------------------------------------- ; Mersenne Twister ; 2001/03/11 Jun Mizutani ; testmt.asm ;--------------------------------------------------------------------- %include "stdio.inc" %include "mt19937.inc" ;============================================================== section .text global _start _start: mov eax, 0x1105 ; 4357(seed) call sgenrand mov ecx, 1000 xor ebx, ebx .loop call genrand push ecx mov ecx, 10 call PrintRightU pop ecx mov eax, ' ' call OutChar inc ebx cmp ebx, 5 jne .skip xor ebx, ebx call NewLine .skip: loop .loop call Exit
初めに eax にシード(ここでは 4357,0は不可)を設定して call sgenrand として準備します. 後は call genrand で eax に 32ビットの乱数が得られます.eax 以外のレジスタは変化しません. 実行結果の一部を示します.
$ ./testmt 2867219139 1585203162 3113124129 2953900839 2463794868 3482265796 1164297043 3598195569 589972756 4112233867 767115311 4093075447 1322433849 3357085324 3300048468 3649464345 3676604632 1475054104 2601934239 3420804864 2492391180 28597038 1901037238 1209433535 3580317774 2488297452 79873538 3308484072 2913896343 4166196021 1930853421 3313543893 2603730014 2827553081 1952080899 1405101208 1959413290 2221997165 4110132150 1025637693
1000個の乱数についてオリジナルの出力結果 mt19937int.out と比較してみます.
$ ./testmt >testmt.out $ diff mt19937int.out testmt.out
何も表示されなければ一致しています. で,一致しました(^^;
前回のサンプルを,移植した mt19937.inc を使って 書き直します.1,000 個の点の表示では速すぎるので 1万倍して 10,000,000 個を 表示しています.また点の色も乱数で決めているため,前回のサンプルの 2万倍の 数の乱数を求めています.
;--------------------------------------------------------------------- ; Frame buffer 2 ; 2001/03/11 Jun Mizutani ; fbtest2.asm ;--------------------------------------------------------------------- %include "stdio.inc" %include "fblib.inc" %include "mt19937.inc" %assign O_RDONLY 00q ;============================================================== section .text global _start _start: ;------------------------------------------- ; フレームバッファの準備 ;------------------------------------------- call fbdev_open ; /dev/fb0 をオープン js near .fb_Error call fb_get_screen ; スクリーン情報を取得 js .fb_Error call fb_copy_scinfo ; scinfo_data を設定 call fb_map_screen ; メモリにマッピング js .fb_Error mov [fb_mem], eax ; 先頭アドレス mov edi, [fb_mem] mov ebx, [mmap_arg.len] ; サイズ ;------------------------------------------- ; [edi] から [edi+ebx-1] の範囲に書き込み ;------------------------------------------- call sgenrand shr ebx, 1 mov ecx, 10000000 .loop: call genrand xor edx, edx div ebx call genrand mov [edi+edx*2], ax ; ランダムに点を打つ loop .loop xor esi, esi mov ecx, ebx shr ecx, 1 .loop2: mov eax, [edi+esi*4] ; 画面読み出し xor eax, 0x55555555 mov [edi+esi*4], eax ; 画面書き込み inc esi loop .loop2 xor esi, esi mov ecx, ebx shr ecx, 1 .loop3: mov eax, [edi+esi*4] ; 画面読み出し xor eax, 0x55555555 mov [edi+esi*4], eax ; 画面書き込み inc esi loop .loop3 ;------------------------------------------- ; フレームバッファの後始末 ;------------------------------------------- .exit call fb_unmap_screen call fbdev_close call Exit ;------------------------------------------- .fb_Error: mov eax, fberr_msg call OutAsciiZ jmp short .exit fberr_msg db 'frabe buffer error!', 10, 0 ;============================================================== section .bss fb_mem resd 1
./fbtest2 として実行すると一瞬で画面がランダムな色の点で埋めつくされます.