素数分布(2)
前にも書いたように、GPUは分岐に弱い。
ある程度まとまったスレッド(16 or 32)が同じ命令を実行するため、
分岐すると、違う命令を実行するスレッドは、
今実行している命令が終わるのを待っていなければならない。
だから、分岐しないようにすれば速くなる。
前回のシェーダプログラムは、
素数かどうかを調べる自然数nを回し、
擬似素数(というか奇数)pを回していて、
2重のループにしていた。
これを一つのループにする。
1回割り算をしたら、次のnとpを計算する。
その際、状況によってnとpは変わってくるが、
これを分岐が起きないように書く。
例えば、
pが5ならnは3増し、その他ならnは2増し、
なら次のように実装する。
n += int(p == 5) + 2;
p == 5をintにキャストすると、trueなら1、falseなら0になる。
万事この調子でコードを書いて、分岐が起きないようにする。
#extension GL_ARB_texture_rectangle : enable
#extension GL_EXT_gpu_shader4 : enableuniform sampler2DRect texUnit0; // data
uniform int nRep;
void main(void) {
int nPrimes = 0;
vec4 color = texRECT(texUnit0, gl_FragCoord.xy);
int iStart = int(color.x);
if(iStart != 0) {
int n = iStart;
int p = 3;
do {
int mod = n % p;
p += 2;
bvec2 b = bvec2(mod == 0, p * p > n);
nPrimes += int(!b.x && b.y);
n += int(any(b)) * 2; // n+2 or n
p += -int(any(b)) * (p - 3); // 3 or p
} while(n < iStart + nRep);
}
gl_FragColor = vec4(nPrimes, 0, 0, 0);
}
CPUより約22倍速く、理想に近い速度となった。
以下、細かい点:
bvec2はboolを2つ並べたもの。
anyはどれかがtrueならtrueを返す。
全てtrueならtrueはall。