前の項目で解説した、ステージ(stage)とパス(pass)はシリアルな処理であり、データ並列化を行う設計とはなっていません。従って忠実にバイトニック整列を実装するのであれば以下のループを避けることはできません。
passes = 0 distance = 0 numberOfStages = np.uint32(np.log2(DATA_SIZE)) #(1) for i in range(numberOfStages-1): #(2) for j in range(passes+1): distance = 1 << (i - j) stageDistance = 1 << i #OpenCLキューにコマンドを挿入 passes+=1 //(3)
log2は独自関数です。ソースコード内のメソッドを参照ください。 | |
ステージの総数は2を底とするデータ要素数の対数から1を引いた数です。 | |
パスはステージを反復する時にインクリメントします。 |
バイトニックマージについては、パスによってスワップする要素の距離を調整えます。スワップの距離は以下のように、ステージからパスを引いた値の累乗となります。
for j in range(passes):
distance = 1 << (numberOfStages - 1 - j)
#OpenCLキューにコマンドを挿入します。
#distanceをカーネルのパラメータとする。データの要素数が64とする場合はステージとパスについては以下の順序で推移していきます。
Stage = 0 Pass = 0 Stage = 1 Pass = 0 Stage = 1 Pass = 1 Stage = 2 Pass = 0 Stage = 2 Pass = 1 Stage = 2 Pass = 2 Stage = 3 Pass = 0 Stage = 3 Pass = 1 Stage = 3 Pass = 2 Stage = 3 Pass = 3 Stage = 4 Pass = 0 Stage = 4 Pass = 1 Stage = 4 Pass = 2 Stage = 4 Pass = 3 Stage = 4 Pass = 4
データ要素数が16の場合は、3つのステージ(log2 N - 1)を処理します。
Stage = 0 Pass = 0 Stage = 1 Pass = 0 Stage = 1 Pass = 1 Stage = 2 Pass = 0 Stage = 2 Pass = 1 Stage = 2 Pass = 2
ホストコードについてはステージとパスがどのように算出されるかを追ってみてネットワーク図に書いてみるとわかるものなので、コードに目を通す前に図を書いて過程を理解するようにしてください。
ホストコードと、カーネルのソースコードは以下のようになります。カーネルのソースは次の項目で解説します。
import pyopencl as cl
import numpy as np
from numpy.random import *
import time
# set the seed to 100
np.random.seed(4)
KERNEL_BITONIC = "bitonic"
KERNEL_MERGE = "merge"
DATA_SIZE = 2097152
global_work_size = (DATA_SIZE, )
data = randint(0, DATA_SIZE, DATA_SIZE).astype(np.uint32)
out = np.zeros(DATA_SIZE).astype(np.uint32)
print(data)
devices = [cl.get_platforms()[0].get_devices(cl.device_type.GPU)[0]]
ctx = cl.Context(devices)
queue = cl.CommandQueue(ctx)
mf = cl.mem_flags
data_mem = cl.Buffer(ctx, mf.USE_HOST_PTR, hostbuf=data)
opt_string = "-Dsize="+str(DATA_SIZE)
options = [opt_string]
program = cl.Program(ctx, """
__kernel void merge(
__global uint* data,
const uint distance) {
uint gid = get_global_id(0);
uint lid = get_local_id(0);
uint cmp_mask;
int in_range = isless(gid % (distance << 1),distance);
if(in_range) {
uint left = data[gid];
uint right = data[gid+distance];
cmp_mask = left < right ? 1 : 0;
data[gid] = select(left,right,cmp_mask);
data[gid+distance] = select(right,left,cmp_mask);
//printf("merge. d=%d, gdiv=%d, left=%d, right=%d, data[gid]=%d, data[gid+d]=%d\\n", distance,gid % (distance << 1),left,right,data[gid],data[gid+distance]);
}
}
__kernel void bitonic(
__global uint* data,
const uint distance,
const uint stageDistance) {
uint gid = get_global_id(0);
uint lid = get_local_id(0);
int in_range = isless(gid % (distance << 1),distance);
if(in_range) {
uint middle = stageDistance << 1;
int dir_mask = isgreaterequal(gid%(middle*2),middle);
uint left = data[gid];
uint right = data[gid+distance];
uint cmp_mask;
if(dir_mask) {
cmp_mask = left < right ? 1 : 0;
} else {
cmp_mask = left > right ? 1 : 0;
}
data[gid] = select(left,right,cmp_mask);
data[gid+distance] = select(right,left,cmp_mask);
//printf("d=%d, gdiv=%d, left=%d, right=%d, data[gid]=%d, data[gid+d]=%d\\n", distance,gid % (distance << 1),left,right,data[gid],data[gid+distance]);
}
}
""").build(options=options)
kernel_bitonic = cl.Kernel(program, name=KERNEL_BITONIC)
kernel_merge = cl.Kernel(program, name=KERNEL_MERGE)
passes = 0
distance = 0
numberOfStages = np.uint32(np.log2(DATA_SIZE))
st = time.time()
for i in range(numberOfStages-1):
for j in range(passes+1):
distance = 1 << (i - j)
stageDistance = 1 << i
local_work_size = (np.clip(distance << 1, 0, 256), )
kernel_bitonic.set_arg(0, data_mem)
kernel_bitonic.set_arg(1, np.int32(distance))
kernel_bitonic.set_arg(2, np.int32(stageDistance))
cl.enqueue_nd_range_kernel(
queue=queue,
kernel=kernel_bitonic,
global_work_size=global_work_size,
local_work_size=local_work_size)
#print("stage = " + str(i) + " pass = " + str(j) + " distance = " + str(distance))
passes += 1
passes = numberOfStages
j = 0
for j in range(passes):
distance = 1 << (numberOfStages - 1 - j)
local_work_size = (np.clip(distance << 1, 0, 256), )
kernel_merge.set_arg(0, data_mem)
kernel_merge.set_arg(1, np.uint32(distance))
#print("distance = " + str(distance) + " pass = " + str(j))
cl.enqueue_nd_range_kernel(
queue=queue,
kernel=kernel_merge,
global_work_size=global_work_size,
local_work_size=local_work_size)
et = time.time()
out = cl.enqueue_map_buffer(
queue=queue,
buf=data_mem,
flags=cl.mem_flags.WRITE_ONLY,
offset=0,
shape=(DATA_SIZE,),
dtype=np.uint32)
#print(out)
print("%s" % (et-st) )
Copyright 2018-2019, by Masaki Komatsu