blob: c45b652d0edf87d96ad25f8c571811229bd8f587 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can
* be found in the LICENSE file.
*
*/
//
//
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
//
// squelch OpenCL 1.2 deprecation warning
//
#ifndef CL_USE_DEPRECATED_OPENCL_1_2_APIS
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
#endif
#include "common/macros.h"
#include "common/cl/assert_cl.h"
#include "common/cl/find_cl.h"
//
//
//
#include "hs_cl.h"
//
// FIXME -- LIMITED TO INTEL / GEN8+ FOR NOW
//
#include "intel/gen8/u32/hs_target.h"
#include "intel/gen8/u64/hs_target.h"
// #include "intel/gen9lp/u32/hs_target.h"
// #include "intel/gen9lp/u64/hs_target.h"
//
// The quality of the RNG doesn't matter. The same number of
// instructions will be run no matter what the key distribution looks
// like. So here is something small and fast.
//
static
uint32_t
hs_rand_u32()
{
static uint32_t seed = 0xDEADBEEF;
// Numerical Recipes
seed = seed * 1664525 + 1013904223;
return seed;
}
//
//
//
static
void
hs_fill_rand(uint32_t * vin_h, uint32_t const count, uint32_t const words)
{
#if 1
for (uint32_t ii=0; ii<count*words; ii++)
vin_h[ii] = hs_rand_u32();
#elif 0 // in-order
memset(vin_h,0,count*words*sizeof(uint32_t));
for (uint32_t ii=0; ii<count; ii++)
vin_h[ii*words] = ii;
#else // reverse order
memset(vin_h,0,count*words*sizeof(uint32_t));
for (uint32_t ii=0; ii<count; ii++)
vin_h[ii*words] = count - 1 - ii;
#endif
}
//
//
//
char const * hs_cpu_sort_u32(uint32_t * a, uint32_t const count, double * const cpu_ns);
char const * hs_cpu_sort_u64(uint64_t * a, uint32_t const count, double * const cpu_ns);
//
//
//
static
char const *
hs_cpu_sort(void * sorted_h,
uint32_t const hs_words,
uint32_t const count,
double * const cpu_ns)
{
if (hs_words == 1)
return hs_cpu_sort_u32(sorted_h,count,cpu_ns);
else
return hs_cpu_sort_u64(sorted_h,count,cpu_ns);
}
static
void
hs_transpose_slabs_u32(uint32_t const hs_words,
uint32_t const hs_width,
uint32_t const hs_height,
uint32_t * vout_h,
uint32_t const count)
{
uint32_t const slab_keys = hs_width * hs_height;
size_t const slab_size = sizeof(uint32_t) * hs_words * slab_keys;
uint32_t * const slab = ALLOCA_MACRO(slab_size);
uint32_t slab_count = count / slab_keys;
while (slab_count-- > 0)
{
memcpy(slab,vout_h,slab_size);
for (uint32_t row=0; row<hs_height; row++)
for (uint32_t col=0; col<hs_width; col++)
vout_h[col * hs_height + row] = slab[row * hs_width + col];
vout_h += slab_keys;
}
}
static
void
hs_transpose_slabs_u64(uint32_t const hs_words,
uint32_t const hs_width,
uint32_t const hs_height,
uint64_t * vout_h,
uint32_t const count)
{
uint32_t const slab_keys = hs_width * hs_height;
size_t const slab_size = sizeof(uint32_t) * hs_words * slab_keys;
uint64_t * const slab = ALLOCA_MACRO(slab_size);
uint32_t slab_count = count / slab_keys;
while (slab_count-- > 0)
{
memcpy(slab,vout_h,slab_size);
for (uint32_t row=0; row<hs_height; row++)
for (uint32_t col=0; col<hs_width; col++)
vout_h[col * hs_height + row] = slab[row * hs_width + col];
vout_h += slab_keys;
}
}
static
void
hs_transpose_slabs(uint32_t const hs_words,
uint32_t const hs_width,
uint32_t const hs_height,
void * vout_h,
uint32_t const count)
{
if (hs_words == 1)
hs_transpose_slabs_u32(hs_words,hs_width,hs_height,vout_h,count);
else
hs_transpose_slabs_u64(hs_words,hs_width,hs_height,vout_h,count);
}
//
//
//
static
void
hs_debug_u32(uint32_t const hs_width,
uint32_t const hs_height,
uint32_t const * vout_h,
uint32_t const count)
{
uint32_t const slab_keys = hs_width * hs_height;
uint32_t const slabs = (count + slab_keys - 1) / slab_keys;
for (uint32_t ss=0; ss<slabs; ss++) {
fprintf(stderr,"%u\n",ss);
for (uint32_t cc=0; cc<hs_height; cc++) {
for (uint32_t rr=0; rr<hs_width; rr++)
fprintf(stderr,"%8" PRIX32 " ",*vout_h++);
fprintf(stderr,"\n");
}
}
}
static
void
hs_debug_u64(uint32_t const hs_width,
uint32_t const hs_height,
uint64_t const * vout_h,
uint32_t const count)
{
uint32_t const slab_keys = hs_width * hs_height;
uint32_t const slabs = (count + slab_keys - 1) / slab_keys;
for (uint32_t ss=0; ss<slabs; ss++) {
fprintf(stderr,"%u\n",ss);
for (uint32_t cc=0; cc<hs_height; cc++) {
for (uint32_t rr=0; rr<hs_width; rr++)
fprintf(stderr,"%16" PRIX64 " ",*vout_h++);
fprintf(stderr,"\n");
}
}
}
//
// Used for benchmarking on out-of-order queues. Attaching an event
// to a kernel on an OOQ with profiling enabled will result in a
// synchronization point and block concurrent execution of kernels.
//
// The workaround that enables measuring the entire runtime of the
// sort is to launch a dummy kernel with an event, a barrier without
// an event, then the call to hs_sort(), followed by a final dummy
// kernel with an event.
//
// The end time of the first dummy and start time of the second dummy
// will provide a conservative estimate of the total execution time of
// the hs_sort() routine.
//
// Note that once kernels are enqueued they are scheduled with only
// microseconds between them so this should only be a small number of
// microseconds longer than the true hs_sort() execution time.
//
#define HS_DUMMY_KERNEL_PROGRAM "kernel void hs_dummy_kernel() { ; }"
static cl_kernel hs_dummy_kernel;
static
void
hs_dummy_kernel_create(cl_context context, cl_device_id device_id)
{
cl_int err;
char const * strings[] = { HS_DUMMY_KERNEL_PROGRAM };
size_t const strings_sizeof[] = { sizeof(HS_DUMMY_KERNEL_PROGRAM) };
cl_program program = clCreateProgramWithSource(context,
1,
strings,
strings_sizeof,
&err); cl_ok(err);
cl(BuildProgram(program,
1,
&device_id,
NULL,
NULL,
NULL));
hs_dummy_kernel = clCreateKernel(program,"hs_dummy_kernel",&err); cl_ok(err);
cl(ReleaseProgram(program));
}
static
void
hs_dummy_kernel_release()
{
cl(ReleaseKernel(hs_dummy_kernel));
}
static
void
hs_dummy_kernel_enqueue(cl_command_queue cq,
uint32_t wait_list_size,
cl_event const * wait_list,
cl_event * event)
{
size_t const global_work_size = 1;
cl(EnqueueNDRangeKernel(cq,
hs_dummy_kernel,
1,
NULL,
&global_work_size,
NULL,
wait_list_size,
wait_list,
event));
}
//
//
//
static
void
hs_bench(cl_context context,
cl_command_queue cq,
cl_command_queue cq_profile,
char const * const device_name,
char const * const driver_version,
uint32_t const hs_words,
uint32_t const hs_width,
uint32_t const hs_height,
struct hs_cl const * const hs,
uint32_t const count_lo,
uint32_t const count_hi,
uint32_t const count_step,
uint32_t const loops,
uint32_t const warmup,
bool const linearize)
{
//
// return if nothing to do
//
if (count_hi <= 1)
return;
//
// size the arrays
//
uint32_t count_hi_padded_in, count_hi_padded_out;
hs_cl_pad(hs,count_hi,&count_hi_padded_in,&count_hi_padded_out);
//
// SIZE
//
size_t const key_size = sizeof(uint32_t) * hs_words;
size_t const size_hi_in = count_hi_padded_in * key_size;
size_t const size_hi_out = count_hi_padded_out * key_size;
//
// ALLOCATE
//
cl_int cl_err;
void * sorted_h = malloc(size_hi_in);
cl_mem random = clCreateBuffer(context,
CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
size_hi_in,
NULL,&cl_err); cl_ok(cl_err);
cl_mem vin = clCreateBuffer(context,
CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
size_hi_in,
NULL,&cl_err); cl_ok(cl_err);
cl_mem vout = clCreateBuffer(context,
CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
size_hi_out,
NULL,&cl_err); cl_ok(cl_err);
//
// BLOCKING MAP AND INIT KEYS
//
{
void * random_h = clEnqueueMapBuffer(cq,
random,
CL_TRUE,
CL_MAP_WRITE_INVALIDATE_REGION,
0,size_hi_in,
0,NULL,NULL,
&cl_err); cl_ok(cl_err);
// fill with random numbers
hs_fill_rand(random_h,count_hi,hs_words);
//
// UNMAP
//
cl(EnqueueUnmapMemObject(cq,random,random_h,0,NULL,NULL));
}
//
// BENCHMARK
//
for (uint32_t count=count_lo; count<=count_hi; count+=count_step)
{
// compute padding before sorting
uint32_t count_padded_in, count_padded_out;
hs_cl_pad(hs,count,&count_padded_in,&count_padded_out);
cl_ulong elapsed_ns_min = UINT64_MAX;
cl_ulong elapsed_ns_max = 0;
cl_ulong elapsed_ns_sum = 0;
cl(EnqueueCopyBuffer(cq,random,vin,0,0,count * key_size,0,NULL,NULL));
cl(Finish(cq));
for (uint32_t ii=0; ii<warmup+loops; ii++)
{
if (ii == warmup)
{
elapsed_ns_min = UINT64_MAX;
elapsed_ns_max = 0;
elapsed_ns_sum = 0;
}
#if 0
//
// optionally, initialize vin on every loop -- no need
//
cl(EnqueueCopyBuffer(cq,random,vin,0,0,count * key_size,0,NULL,NULL));
cl(Finish(cq));
#endif
//
// sort vin
//
cl_event start, complete, end;
hs_dummy_kernel_enqueue(cq_profile,0,NULL,&start);
// note hs_sort enqueues a final barrier
hs_cl_sort(hs,
cq,
1,&start,&complete,
vin,vout,
count,
count_padded_in,
count_padded_out,
linearize);
hs_dummy_kernel_enqueue(cq_profile,1,&complete,&end);
cl(Finish(cq_profile));
//
// measure duration
//
cl_ulong t_start=0, t_end=0;
// start
cl(GetEventProfilingInfo(start,
CL_PROFILING_COMMAND_END,
sizeof(cl_ulong),
&t_start,
NULL));
// end
cl(GetEventProfilingInfo(end,
CL_PROFILING_COMMAND_START,
sizeof(cl_ulong),
&t_end,
NULL));
cl_ulong const t = t_end - t_start;
elapsed_ns_min = MIN_MACRO(elapsed_ns_min,t);
elapsed_ns_max = MAX_MACRO(elapsed_ns_max,t);
elapsed_ns_sum += t;
cl(ReleaseEvent(start));
cl(ReleaseEvent(complete));
cl(ReleaseEvent(end));
}
//
// COPY KEYS BACK FOR VERIFICATION
//
size_t const size_padded_in = count_padded_in * key_size;
void * vin_h = clEnqueueMapBuffer(cq,
vin,
CL_FALSE,
CL_MAP_READ,
0,size_padded_in,
0,NULL,NULL,
&cl_err); cl_ok(cl_err);
void * vout_h = clEnqueueMapBuffer(cq,
vout,
CL_FALSE,
CL_MAP_READ,
0,size_padded_in,
0,NULL,NULL,
&cl_err); cl_ok(cl_err);
cl(Finish(cq));
//
// SORT THE UNTOUCHED RANDOM INPUT
//
memcpy(sorted_h,vin_h,size_padded_in);
double cpu_ns;
char const * const algo = hs_cpu_sort(sorted_h,hs_words,count_padded_in,&cpu_ns);
//
// EXPLICITLY TRANSPOSE THE CPU SORTED SLABS IF NOT LINEARIZING
//
if (!linearize)
hs_transpose_slabs(hs_words,hs_width,hs_height,vout_h,count_padded_in);
//
// VERIFY
//
bool const verified = memcmp(sorted_h,vout_h,size_padded_in) == 0;
#ifndef NDEBUG
if (!verified)
{
if (hs_words == 1)
hs_debug_u32(hs_width,hs_height,vout_h,count);
else // ulong
hs_debug_u64(hs_width,hs_height,vout_h,count);
}
#endif
cl(EnqueueUnmapMemObject(cq,vin, vin_h, 0,NULL,NULL));
cl(EnqueueUnmapMemObject(cq,vout,vout_h,0,NULL,NULL));
cl(Finish(cq));
//
// REPORT
//
fprintf(stdout,"%s, %s, %s, %s, %s, %8u, %8u, %8u, CPU, %s, %9.2f, %6.2f, GPU, %9u, %7.3f, %7.3f, %7.3f, %6.2f, %6.2f\n",
device_name,
driver_version,
(hs_words == 1) ? "uint" : "ulong",
linearize ? "linear" : "slab",
verified ? " OK " : "*FAIL*",
count,
count_padded_in,
count_padded_out,
// CPU
algo,
cpu_ns / 1000000.0, // milliseconds
1000.0 * count / cpu_ns, // mkeys / sec
// GPU
loops,
elapsed_ns_sum / 1000000.0 / loops, // avg msecs
elapsed_ns_min / 1000000.0, // min msecs
elapsed_ns_max / 1000000.0, // max msecs
1000.0 * count * loops / elapsed_ns_sum, // mkeys / sec - avg
1000.0 * count / elapsed_ns_min); // mkeys / sec - max
// quit early if not verified
if (!verified)
break;
}
//
// dispose
//
cl(ReleaseMemObject(vout));
cl(ReleaseMemObject(vin));
cl(ReleaseMemObject(random));
free(sorted_h);
}
//
//
//
int
main(int argc, char const * argv[])
{
char const * const target_platform_substring = "Intel";
char const * const target_device_substring = "Graphics";
//
// find platform and device ids
//
cl_platform_id platform_id;
cl_device_id device_id;
#define HS_DEVICE_NAME_SIZE 64
char device_name[HS_DEVICE_NAME_SIZE];
size_t device_name_size;
cl(FindIdsByName(target_platform_substring,
target_device_substring,
&platform_id,
&device_id,
HS_DEVICE_NAME_SIZE,
device_name,
&device_name_size,
true));
//
// create context
//
cl_context_properties context_properties[] =
{
CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id,
0
};
cl_int cl_err;
cl_context context = clCreateContext(context_properties,
1,
&device_id,
NULL,
NULL,
&cl_err);
cl_ok(cl_err);
//
// create command queue
//
#if 0 // OPENCL 2.0
cl_queue_properties props[] = {
CL_QUEUE_PROPERTIES,
(cl_queue_properties)CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE,
#ifndef NDEBUG
(cl_queue_properties)CL_QUEUE_PROFILING_ENABLE,
#endif
0
};
cl_queue_properties props_profile[] = {
CL_QUEUE_PROPERTIES,
(cl_queue_properties)CL_QUEUE_PROFILING_ENABLE,
0
};
cl_command_queue cq = clCreateCommandQueueWithProperties(context,
device_id,
props,
&cl_err); cl_ok(cl_err);
cl_command_queue cq_profile = clCreateCommandQueueWithProperties(context,
device_id,
props_profile,
&cl_err); cl_ok(cl_err);
#else // OPENCL 1.2
cl_command_queue cq = clCreateCommandQueue(context,
device_id,
#ifndef NDEBUG
CL_QUEUE_PROFILING_ENABLE |
#endif
CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE,
&cl_err); cl_ok(cl_err);
cl_command_queue cq_profile = clCreateCommandQueue(context,
device_id,
CL_QUEUE_PROFILING_ENABLE,
&cl_err); cl_ok(cl_err);
#endif
//
// Intel GEN workaround -- create dummy kernel for semi-accurate
// profiling on an out-of-order queue.
//
hs_dummy_kernel_create(context,device_id);
//
// select the target
//
uint32_t const key_val_words = (argc == 1) ? 2 : strtoul(argv[1],NULL,0);
struct hs_cl_target const * hs_target;
if (key_val_words == 1)
hs_target = &hs_intel_gen8_u32;
else
hs_target = &hs_intel_gen8_u64;
//
// create kernels
//
fprintf(stdout,"Creating... ");
struct hs_cl * const hs = hs_cl_create(hs_target,context,device_id);
fprintf(stdout,"done.\n");
//
//
//
#ifdef NDEBUG
#define HS_BENCH_LOOPS 100
#define HS_BENCH_WARMUP 100
#else
#define HS_BENCH_LOOPS 1
#define HS_BENCH_WARMUP 0
#endif
//
// sort sizes and loops
//
uint32_t const kpb = hs_target->config.slab.height << hs_target->config.slab.width_log2;
uint32_t const count_lo = (argc <= 2) ? kpb : strtoul(argv[2],NULL,0);
uint32_t const count_hi = (argc <= 3) ? count_lo : strtoul(argv[3],NULL,0);
uint32_t const count_step = (argc <= 4) ? count_lo : strtoul(argv[4],NULL,0);
uint32_t const loops = (argc <= 5) ? HS_BENCH_LOOPS : strtoul(argv[5],NULL,0);
uint32_t const warmup = (argc <= 6) ? HS_BENCH_WARMUP : strtoul(argv[6],NULL,0);
bool const linearize = (argc <= 7) ? true : strtoul(argv[7],NULL,0);
//
// labels
//
fprintf(stdout,
"Device, "
"Driver, "
"Type, "
"Slab/Linear, "
"Verified?, "
"Keys, "
"Keys Padded In, "
"Keys Padded Out, "
"CPU Algorithm, "
"CPU Msecs, "
"CPU Mkeys/s, "
"Trials, "
"Avg. Msecs, "
"Min Msecs, "
"Max Msecs, "
"Avg. Mkeys/s, "
"Max. Mkeys/s\n");
//
// we want to track driver versions
//
size_t driver_version_size;
cl(GetDeviceInfo(device_id,
CL_DRIVER_VERSION,
0,
NULL,
&driver_version_size));
char * const driver_version = ALLOCA_MACRO(driver_version_size);
cl(GetDeviceInfo(device_id,
CL_DRIVER_VERSION,
driver_version_size,
driver_version,
NULL));
//
// benchmark
//
hs_bench(context,
cq,cq_profile,
device_name,
driver_version,
hs_target->config.words.key + hs_target->config.words.val,
1 << hs_target->config.slab.width_log2,
hs_target->config.slab.height,
hs,
count_lo,
count_hi,
count_step,
loops,
warmup,
linearize);
//
// release everything
//
hs_cl_release(hs);
hs_dummy_kernel_release();
cl(ReleaseCommandQueue(cq));
cl(ReleaseCommandQueue(cq_profile));
cl(ReleaseContext(context));
return 0;
}