前の項目で解説した、ステージ(stage)とパス(pass)はシリアルな処理であり、データ並列化を行う設計とはなっていません。従って忠実にバイトニック整列を実装するのであれば以下のループを避けることはできません。
int numberOfStages = log2(DATA_SIZE); //(1) int passes = 0; for(int i = 0; i < numberOfStages-1; i++) { //(2) for(int j = 0; j < passes+1; j++) { //OpenCLキューにコマンドを挿入 } passes++; //(3) }
log2は独自関数です。Javaソースコード内のメソッドを参照ください。 | |
ステージの総数は2を底とするデータ要素数の対数から1を引いた数です。 | |
パスはステージを反復する時にインクリメントします。 |
バイトニックマージについては、パスによってスワップする要素の距離を調整えます。スワップの距離は以下のように、ステージからパスを引いた値の累乗となります。
for(int j = 0; j < passes; j++) { int 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
ホストコードについてはステージとパスがどのように算出されるかを追ってみてネットワーク図に書いてみるとわかるものなので、コードに目を通す前に図を書いて過程を理解するようにしてください。
ホストコードと、カーネルのソースコードは以下のようになります。カーネルのソースは次の項目で解説します。
package com.book.jocl.bitonic; import static org.jocl.CL.*; import java.io.File; import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Paths; import java.util.Scanner; import org.jocl.CL; import org.jocl.Pointer; import org.jocl.Sizeof; import org.jocl.cl_command_queue; import org.jocl.cl_context; import org.jocl.cl_context_properties; import org.jocl.cl_device_id; import org.jocl.cl_kernel; import org.jocl.cl_mem; import org.jocl.cl_platform_id; import org.jocl.cl_program; public class BitonicSortTest { private static final String KERNEL_PATH = "bitonic.cl"; private static final String KERNEL_BITONIC = "bitonic"; private static final String KERNEL_MERGE = "merge"; private static cl_context context; private static cl_command_queue queue; private static cl_program program; private static cl_kernel kernel_bitonic; private static cl_kernel kernel_merge; private static final int DATA_SIZE = 32; private static final int[] data = new int[DATA_SIZE]; private static long[] global_work_size = new long[]{DATA_SIZE,1,1}; private static int log2(int b) { int result = 0; if((b & 0xffff0000) != 0) { b >>>= 16; result = 16; } if(b >= 256) { b >>>= 8; result += 8; } if(b >= 16) { b >>>= 4; result += 4; } if(b >= 4) { b >>>= 2; result += 2; } return result + (b >>> 1); } public static void main(String[] args) throws Exception { CL.setExceptionsEnabled(true); cl_platform_id[] platform = new cl_platform_id[1]; cl_device_id[] device = new cl_device_id[1]; int[] num_devices = new int[1]; clGetPlatformIDs(1, platform, null); clGetDeviceIDs(platform[0], CL_DEVICE_TYPE_GPU, 1, device, num_devices); cl_context_properties props = new cl_context_properties(); props.addProperty(CL_CONTEXT_PLATFORM, platform[0]); context = clCreateContext(props, 1, device, null, null, null); queue = clCreateCommandQueue(context, device[0], 0, null); StringBuffer sb = new StringBuffer(); URL resource = BitonicSortTest.class.getResource(KERNEL_PATH) ; String path = Paths.get(resource.toURI()).toFile().getAbsolutePath(); Scanner sc = new Scanner(new File(path)); while(sc.hasNext()) { sb.append(sc.nextLine() + "\n"); } sc.close(); program = clCreateProgramWithSource(context, 1, new String[] {sb.toString()}, null, null); StringBuffer op = new StringBuffer(); op.append("-Dsize="); op.append(DATA_SIZE); String option = op.toString(); clBuildProgram(program, 0, null, option, null, null); kernel_bitonic = clCreateKernel(program, KERNEL_BITONIC, null); kernel_merge = clCreateKernel(program, KERNEL_MERGE, null); generateSample(); cl_mem data_mem = clCreateBuffer(context, CL_MEM_USE_HOST_PTR, Sizeof.cl_uint*DATA_SIZE, Pointer.to(data), null); int[] passPtr = new int[1]; int[] distancePtr = new int[1]; int[] stageDistancePtr = new int[1]; int numberOfStages = log2(DATA_SIZE); int passes = 0; for(int i = 0; i < numberOfStages-1; i++) { for(int j = 0; j < passes+1; j++) { int distance = 1 << (i - j); distancePtr[0] = distance; stageDistancePtr[0] = 1 << i; long[] local_work_size = new long[]{Math.max(0, Math.min(256, distance<<1)),1,1}; clSetKernelArg(kernel_bitonic, 0, Sizeof.cl_mem, Pointer.to(data_mem)); clSetKernelArg(kernel_bitonic, 1, Sizeof.cl_uint, Pointer.to(distancePtr)); clSetKernelArg(kernel_bitonic, 2, Sizeof.cl_uint, Pointer.to(stageDistancePtr)); clEnqueueNDRangeKernel(queue, kernel_bitonic, 1, null, global_work_size, local_work_size, 0, null, null); System.out.println("Stage = "+i+" Pass = "+j); } passes++; passPtr[0] = passes; } passes = numberOfStages; for(int j = 0; j < passes; j++) { int distance = 1 << (numberOfStages - 1 - j); distancePtr[0] = distance; long[] local_work_size = new long[]{Math.max(0, Math.min(256, distance<<1)),1,1}; clSetKernelArg(kernel_merge, 0, Sizeof.cl_mem, Pointer.to(data_mem)); clSetKernelArg(kernel_merge, 1, Sizeof.cl_uint, Pointer.to(distancePtr)); clEnqueueNDRangeKernel(queue, kernel_merge, 1, null, global_work_size, local_work_size, 0, null, null); } ByteBuffer output = clEnqueueMapBuffer(queue, data_mem, CL_TRUE, CL_MAP_WRITE, 0, Sizeof.cl_uint*DATA_SIZE, 0, null, null, null); clEnqueueUnmapMemObject(queue, data_mem, output, 0, null, null); clFinish(queue); output.order(ByteOrder.LITTLE_ENDIAN); for(int i = 0; i < DATA_SIZE; i++) { System.out.println(output.getInt()); } } private static void generateSample() { for(int i = 0; i < DATA_SIZE; i+=2) { data[i] = DATA_SIZE-i; data[i+1] =10; } } }
__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]); } }
Copyright 2018-2019, by Masaki Komatsu