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 として実行すると一瞬で画面がランダムな色の点で埋めつくされます.