Try   HackMD

イントロ

pokefinderの実装を見に行ったらなんか短かった。
試してみたら、SIMDを抜きにしても前に書いた記事の実装の倍くらい速かった。

調べてみたらXoroshiroのオリジナル実装でも同じようにしてジャンプ関数が提供されていた(ただし固定長のみ)。

考察しようと思ったモチベーション

2倍程度の速度向上だけであれば後回しにしていたと思う。
しかし、既存の実装のネックであった空間計算量(128bit * 128次配列 * 64)が大きく改善されるので、取り組まないわけにはいかなかった。

どういう原理なのか

任意のベクトル

xに対し、
f0(x),f1(x),,f127(x)
は一次独立になる[1]、すなわち基底になる。つまり、Xoroshiro128pやXorShiftの任意の状態ベクトルは、任意に固定したベクトル
x
について
c0f0(x)+c1f1(x)++c127f127(x) (ci{0,1})
の形式で表現される。

基準として取ったベクトルを

x0とし、
xn=fn(x0)
を表現したときの係数を
{ci}
とする。この
{ci}
x0
の取り方には依らず、ただ
n
の値のみによって定まる。これは基準とするベクトルを
x0=fk(x0)
に取り換えたとしても、
f
が線形写像であることにより

xn=fn(x0)=fn(fk(x0))=fk(fn(x0))=fk(c0f0(x0)+c1f1(x0)++c127f127(x0))=c0fk(x0)+c1fk+1(x0)++c127fk+127(x0)=c0f0(x0)+c1f1(x0)++c127f127(x0)

が成り立つからである。

この

{ci}
128=27,28,29,
についてあらかじめ計算しておけば、任意の
n
に対して
log2(n)
回の小ジャンプの組み合わせてジャンプが可能になる。

係数の求め方

2kに対応する係数
{ci}
は以下のようにして求められる。

  • 適当なベクトル
    x
    から基底
    x0=f0(x), x1=f(x), , x127=f127(x)
    を計算する。
  • xi
    を縦ベクトルと見なし、横に並べて
    128
    次正方行列
    T
    を作る。
  • x2k
    を横ベクトルと見なし、
    ci
    も横ベクトル
    c
    と見なせば、
    x2k=cT
    の関係が成り立っており、
    T
    は正則。
  • T1
    を求め、
    x2kT1
    を計算すれば
    c={ci}
    が得られる。

Tips

  • x2k+1
    x2k
    に対して
    2k
    ジャンプを適用させれば得られる。
  • f128
    の小ジャンプは普通に128回回したほうが速い。
  • f128
    から
    f256
    までの計算回数も128回なので、
    n<256
    であれば線形に進めたほうが速い。
    • pokefinderでの実装では
      f128
      の小ジャンプは線形に進めているが、
      f128
      から
      f256
      へのジャンプは線形結合による計算を行ってしまっている。

計算するプログラムと計算結果

https://gist.github.com/yatsuna827/fe396453d474ba7727497fae4418a792

その他

  • こういうのこそPythonで書いた方が楽なのかもしれん。
  • MTやSFMTでも理論上は同じやり方でジャンプ関数が実装できるけど、次元が大きいので逆行列を求める部分に工夫が必要になるし、何より19937消費以内ならfor文で回すほうが速い。

  1. 松本眞 (2004)「 有限体の擬似乱数への応用 」の定理4.20による。

    c0f0(x)+c1f1(x)++c127f127(x)が原始多項式になることは命題4.10を見よ。 ↩︎