在我的笔记本电脑上,我有两张显卡 - 英特尔Iris和Nvidia GeForce GT 750M。我正在尝试使用OpenCL
进行简单的向量添加。我知道,Nvidia卡更快,可以更好地完成工作。原则上,我可以在代码中添加if
语句,该代码将在NVIDIA
属性中查找VENDOR
。但我想要有一些优雅的东西。在OpenCL C/C++
中以编程方式选择更好(更快)GPU的最佳方法是什么?
答案 0 :(得分:6)
我开发了一个实时光线跟踪器(不仅仅是一个光线施法者),它以编程方式选择了两个GPU和一个CPU,并实时渲染和平衡所有三个上的负载。我就是这样做的。
我们假设有三种设备d1
,d2
和d3
。为每台设备分配一个权重:w1
,w2
和w3
。调用要渲染的像素数n
。假设一个名为alpha
的免费参数。
alpha = 0.5
。n1=w1*n
上的第一个d1
像素,n2=w2*n
上的下一个d2
像素以及n3=w3*n
上的最后d3
个像素记录为每个deivce t1
,t2
和t3
呈现的时间。 vsum = n1/t1 + n2/t2 + n3/t3
。w_i = alpha*w_i + (1-alpha)*n_i/t_i/vsum
。值alpha
的点是允许平滑过渡。而不是根据它在某些旧重量中混合的时间重新分配所有重量。没有使用alpha
我得到了不稳定性。可以调整值alpha
。在实践中,它可能设置在1%左右但不是0%。
让我们选择一个例子。
我有一台GTX 590,这是一款带有两个欠频GTX580的双GPU卡。我还有一台Sandy Bridge 2600K处理器。 GPU比CPU快得多。让我们假设他们快了大约10倍。我们还说有900像素。
使用GPU1渲染前300个像素,使用GPU2渲染下300个像素,使用CPU1渲染最后300个像素,并分别记录10 s, 10 s, and 100 s
的时间。因此,整个图像的一个GPU需要30秒,而单独的CPU需要300秒。两个GPUS都将采用15 s
。
计算vsum = 30 + 30 + 3 = 63
。再次重新计算重量:
w1,w2 = 0.5*(1/3) + 0.5*300/10/63 = 0.4
和w3 = 0.5*(1/3) + 0.5*300/100/63 = 0.2
。
渲染下一帧:GPU1为360像素,GPU2为360像素,CPU1为180像素,时间变得更平衡,如11 s, 11 s, and 55 s
。
在多个帧之后,(1-alpha)
术语占主导地位,直到最终权重全部基于该术语。在这种情况下,权重分别变为47%(427像素),47%,6%(46像素),并且时间分别变为14 s, 14 s, 14 s
。在这种情况下,CPU仅改善仅使用GPU一秒钟的结果。
我在此计算中假设均匀负荷。在实际光线跟踪器中,负载随扫描线和像素而变化,但算法在确定权重时保持不变。
在实践中,一旦发现重量,除非场景的负荷变化很大,否则它们不会发生太大变化,例如:如果场景的一个区域具有高折射和反射,其余区域是漫反射的,但即使在这种情况下,我也限制树深度,因此这不会产生戏剧性的效果。
通过循环将此方法扩展到多个设备很容易。我曾在四台设备上测试了我的光线跟踪器。两个12核Xeon CPU和两个GPU。在这种情况下,CPU的影响力要大得多,但GPU仍占主导地位。
如果有人想知道。我为每个设备创建了一个上下文,并在一个单独的线程中使用每个上下文(使用pthreads)。对于三个设备,我使用了三个线程。
实际上,您可以使用它在不同供应商的同一设备上运行。例如,我在2600K上同时使用了AMD和Intel CPU驱动程序(每个驱动程序产生大约一半的帧),看看哪个供应商更好。当我第一次这样做(2012年)时,如果我没记错的话,AMD在英特尔CPU上讽刺地击败了英特尔。
如果有人对我如何提出重量公式感兴趣,我会使用物理学的想法(我的背景是物理学而非编程)。
速度(v
)=距离/时间。在这种情况下,距离(d
)是要处理的像素数。那么总距离是
d = v1*t1 + v2*t2 + v3*t3
我们希望他们每个人都能在同一时间完成
d = (v1 + v2 + v3)*t
然后得到权重定义
v_i*t = w_i*d
给出了
w_i = v_i*t/d
并从(t/d
)替换(d = (v1 + v2 + v3)*t
)给出:
w_i = v_i /(v1 + v2 + v3)
很容易看出这可以推广到任意数量的设备k
w_i = v_i/(v1 + v2 + ...v_k)
因此,我的算法中的vsum
代表"速度之和"。最后,因为v_i
是一段时间内的像素,所以n_i/t_i
最终会给出
w_i = n_i/t_i/(n1/t1 + n2/t2 + ...n_k/t_k)
这是我计算权重的公式中的第二项。
答案 1 :(得分:3)
如果它只是一个矢量添加而你的应用程序驻留在主机端,则cpu将获胜。或者甚至更好,集成的CPU会更快。总体性能取决于algortihms,opencl缓冲区类型(use_host_ptr,read_write等)和计算数据比。即使你不复制但是固定阵列和访问,cpu的延迟也会比pci-e延迟小。
如果您打算使用opengl + opencl interop,那么您需要知道您的计算设备是否与渲染输出设备相同。 (如果你的屏幕从igpu获取数据,那么它就是虹膜,如果不是那么它就是nvidia)
如果你只需要对c ++数组(主机端)进行一些操作并以最快的方式获得结果,那么我建议你“负载平衡”。
使用Iris Pro和两个gt750m(一个超频10%)在Core i7-5775C上添加4k元素的示例
首先,为所有设备提供相同数量的ndrange rages。在每个计算阶段结束时,检查时间。
CPU iGPU dGPU-1 dGPU-2 oc
Intel Intel Nvidia Nvidia
1024 1024 1024 1024
34 ms 5ms 10ms 9ms
然后计算加权(取决于最后的ndrange范围)但放宽(不精确但接近)计算带宽的近似值并相应地改变ndrange范围:
Intel Intel Nvidia Nvidia
512 1536 1024 1024
16 ms 8ms 10ms 9ms
然后继续计算,直到它真的变得稳定。
Intel Intel Nvidia Nvidia
256 1792 1024 1024
9ms 10ms 10ms 9ms
或直到你可以启用更精细的谷物。
Intel Intel Nvidia Nvidia
320 1728 1024 1024
10ms 10ms 10ms 9ms
Intel Intel Nvidia Nvidia
320 1728 960 1088
10ms 10ms 10ms 10ms
^ ^
| |
| PCI-E bandwidth not more than 16 GB/s per device
closer to RAM, better bandwidth (20-40 GB/s) and less kernel overhead
除了获得最新的平衡迭代外,您还可以获得最后10个结果的平均值(或PID),以消除误导平衡的峰值。缓冲副本也可能比计算花费更多时间,如果将其包含在平衡中,您可以关闭不必要/不受益的设备。
如果您创建了一个库,那么您就不必为您的每个新项目尝试基准测试。当您加速矩阵乘法,流体移动,sql表连接和财务近似时,它们将在设备之间自动平衡。
对于平衡的解决方案:
如果您可以将线性系统解析为n个未知数(每个设备的负载数)和n个方程(所有设备的基准结果),则可以一步找到目标负载。如果选择迭代,则需要更多步骤才能收敛。后者并不比写基准更难。前者对我来说更难,但随着时间的推移应该更有效率。
Althogh一个vector-add-only内核不是真实世界的场景,这是我系统的真正基准:
__kernel void bench(__global float * a, __global float *b, __global float *c)
{
int i=get_global_id(0);
c[i]=a[i]+b[i];
}
2560 768 768
AMD FX(tm)-8150 Eight-Core Processor Oland Pitcairn
这是经过几次迭代后(即使使用额外的缓冲区副本,fx也更快,不使用任何主机指针)。即使是oland gpu也在捕捉pitcairn,因为他们的pci-e带宽相同。
现在有了一些三角函数:
__kernel void bench(__global float * a, __global float *b, __global float *c)
{
int i=get_global_id(0);
c[i]=sin(a[i])+cos(b[i])+sin(cos((float)i));
}
1792 1024 1280
测试gddr3-128bit vs gddr5-256bit(超频)和缓存。
__kernel void bench(__global float * a, __global float *b, __global float *c)
{
int i=get_global_id(0);
c[i]=a[i]+b[i]-a[i]-b[i]+a[i]+b[i]-b[i]-a[i]+b[i];
for(int j=0;j<12000;j++)
c[i]+=a[i]+b[i]-a[i]-b[i]+a[i]+b[i]-b[i]-a[i]+b[i];
}
256 256 3584
高计算数据比率:
__kernel void bench(__global float * a, __global float *b, __global float *c)
{
int i=get_global_id(0);
c[i]=0.0f; float c0=c[i];float a0=a[i];float b0=b[i];
for(int j=0;j<12000;j++)
c0+=sin(a0)+cos(b0*a0)+cos(sin(b0)*19.95f);
c[i]=c0;
}
256 2048 1792
现在Oland gpu再次值得,即使只有320核心也能获胜。因为4k元素容易缠绕在所有320个核心上超过10次但是pitcairn gpu(1280个核心)没有完全填充折叠阵列(波前),这导致执行单元的占用率降低---&gt;无法隐藏延迟。 我认为低负载的低端设备更好。也许我可以在directx-12推出一些负载均衡器时使用它,这个Oland可以在游戏爆炸中计算5000 - 10000粒子的物理特性,而pitcairn可以计算烟雾密度。
答案 2 :(得分:1)
好:只需选择第一个兼容设备即可。在大多数系统中,只有一个。
更好:您可以非常粗略通过将CL_DEVICE_MAX_COMPUTE_UNITS设备信息结果乘以CL_DEVICE_MAX_CLOCK_FREQUENCY设备信息结果来估算设备性能。根据您的工作负载,您可能希望包含其他指标,例如内存大小。您可以根据工作量来混合这些。
最佳:在每台设备上使用您的确切工作流程进行基准测试。这是确实知道的唯一方法,因为其他任何东西都只是猜测。
最后,用户可能会关心您使用的是哪个GPU,因此无论您选择哪种方法,都应该有一些方法来覆盖您的编程选择。
答案 3 :(得分:1)
看看下面这段代码来识别GPU:
#include <iostream>
#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#pragma comment (lib, "x86_64/opencl.lib")
#endif
//OpenCL saxpy kernel used for benchmarking
const char* saxpy_kernel =
"__kernel \n"
"void saxpy_kernel(float alpha, \n"
" __global float *A, \n"
" __global float *B, \n"
" __global float *C) \n"
"{ \n"
" int idx = get_global_id(0); \n"
" C[idx] = alpha * A[idx] + B[idx]; \n"
"} ";
const char* clErrName[] = {
"CL_SUCCESS", //0
"CL_DEVICE_NOT_FOUND", //-1
"CL_DEVICE_NOT_AVAILABLE", //-2
"CL_COMPILER_NOT_AVAILABLE", //-3
"CL_MEM_OBJECT_ALLOCATION_FAILURE", //-4
"CL_OUT_OF_RESOURCES", //-5
"CL_OUT_OF_HOST_MEMORY", //-6
"CL_PROFILING_INFO_NOT_AVAILABLE", //-7
"CL_MEM_COPY_OVERLAP", //-8
"CL_IMAGE_FORMAT_MISMATCH", //-9
"CL_IMAGE_FORMAT_NOT_SUPPORTED", //-10
"CL_BUILD_PROGRAM_FAILURE", //-11
"CL_MAP_FAILURE", //-12
"CL_MISALIGNED_SUB_BUFFER_OFFSET", //-13
"CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST", //-14
"CL_COMPILE_PROGRAM_FAILURE", //-15
"CL_LINKER_NOT_AVAILABLE", //-16
"CL_LINK_PROGRAM_FAILURE", //-17
"CL_DEVICE_PARTITION_FAILED", //-18
"CL_KERNEL_ARG_INFO_NOT_AVAILABLE", //-19
"CL_UNDEFINED_ERROR_20", //-20
"CL_UNDEFINED_ERROR_21", //-21
"CL_UNDEFINED_ERROR_22", //-22
"CL_UNDEFINED_ERROR_23", //-23
"CL_UNDEFINED_ERROR_24", //-24
"CL_UNDEFINED_ERROR_25", //-25
"CL_UNDEFINED_ERROR_26", //-26
"CL_UNDEFINED_ERROR_27", //-27
"CL_UNDEFINED_ERROR_28", //-28
"CL_UNDEFINED_ERROR_29", //-29
"CL_INVALID_VALUE", //-30
"CL_INVALID_DEVICE_TYPE", //-31
"CL_INVALID_PLATFORM", //-32
"CL_INVALID_DEVICE", //-33
"CL_INVALID_CONTEXT", //-34
"CL_INVALID_QUEUE_PROPERTIES", //-35
"CL_INVALID_COMMAND_QUEUE", //-36
"CL_INVALID_HOST_PTR", //-37
"CL_INVALID_MEM_OBJECT", //-38
"CL_INVALID_IMAGE_FORMAT_DESCRIPTOR", //-39
"CL_INVALID_IMAGE_SIZE", //-40
"CL_INVALID_SAMPLER", //-41
"CL_INVALID_BINARY", //-42
"CL_INVALID_BUILD_OPTIONS", //-43
"CL_INVALID_PROGRAM", //-44
"CL_INVALID_PROGRAM_EXECUTABLE", //-45
"CL_INVALID_KERNEL_NAME", //-46
"CL_INVALID_KERNEL_DEFINITION", //-47
"CL_INVALID_KERNEL", //-48
"CL_INVALID_ARG_INDEX", //-49
"CL_INVALID_ARG_VALUE", //-50
"CL_INVALID_ARG_SIZE", //-51
"CL_INVALID_KERNEL_ARGS", //-52
"CL_INVALID_WORK_DIMENSION", //-53
"CL_INVALID_WORK_GROUP_SIZE", //-54
"CL_INVALID_WORK_ITEM_SIZE", //-55
"CL_INVALID_GLOBAL_OFFSET", //-56
"CL_INVALID_EVENT_WAIT_LIST", //-57
"CL_INVALID_EVENT", //-58
"CL_INVALID_OPERATION", //-59
"CL_INVALID_GL_OBJECT", //-60
"CL_INVALID_BUFFER_SIZE", //-61
"CL_INVALID_MIP_LEVEL", //-62
"CL_INVALID_GLOBAL_WORK_SIZE", //-63
"CL_INVALID_PROPERTY", //-64
"CL_INVALID_IMAGE_DESCRIPTOR", //-65
"CL_INVALID_COMPILER_OPTIONS", //-66
"CL_INVALID_LINKER_OPTIONS", //-67
"CL_INVALID_DEVICE_PARTITION_COUNT", //-68
"CL_INVALID_PIPE_SIZE", //-69
"CL_INVALID_DEVICE_QUEUE", //-70
};
const int MAX_ERR_CODE = 70;
inline bool __clCallSuccess(cl_int err_code, const char* source_file, const int source_line)
{
if (err_code == CL_SUCCESS)
return true;
if ((err_code > 0) || (err_code < -MAX_ERR_CODE))
std::clog << "\t - unknown CL error: " << err_code;
else
std::clog << "\t - CL call error: " << clErrName[-err_code];
std::clog << " [" << source_file << " : " << source_line << "]" << std::endl;
return false;
}
#define clCallSuccess(err_code) __clCallSuccess(err_code, __FILE__, __LINE__)
float cl_BenchmarkDevice(cl_context context, cl_command_queue command_queue, cl_device_id device_id)
{
float microSeconds = -1.;
int i;
cl_int clStatus;
const int VECTOR_SIZE = 512 * 1024;
// Allocate space for vectors A, B and C
float* A = (float*)malloc(sizeof(float) * VECTOR_SIZE); if(A) {
float* B = (float*)malloc(sizeof(float) * VECTOR_SIZE); if(B) {
float* C = (float*)malloc(sizeof(float) * VECTOR_SIZE); if(C) {
for (i = 0; i < VECTOR_SIZE; i++)
{
A[i] = (float)i;
B[i] = (float)(VECTOR_SIZE - i);
C[i] = 0;
}
// Create memory buffers on the device for each vector
cl_mem A_clmem = clCreateBuffer(context, CL_MEM_READ_ONLY, VECTOR_SIZE * sizeof(float), NULL, &clStatus); if (clCallSuccess(clStatus)) {
cl_mem B_clmem = clCreateBuffer(context, CL_MEM_READ_ONLY, VECTOR_SIZE * sizeof(float), NULL, &clStatus); if (clCallSuccess(clStatus)) {
cl_mem C_clmem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, VECTOR_SIZE * sizeof(float), NULL, &clStatus); if (clCallSuccess(clStatus)) {
// Copy the Buffer A and B to the device
clStatus = clEnqueueWriteBuffer(command_queue, A_clmem, CL_TRUE, 0, VECTOR_SIZE * sizeof(float), A, 0, NULL, NULL); if (clCallSuccess(clStatus)) {
clStatus = clEnqueueWriteBuffer(command_queue, B_clmem, CL_TRUE, 0, VECTOR_SIZE * sizeof(float), B, 0, NULL, NULL); if (clCallSuccess(clStatus)) {
// Create a program from the kernel source and build it
cl_program program = clCreateProgramWithSource(context, 1, (const char**)&saxpy_kernel, NULL, &clStatus); if (clCallSuccess(clStatus) && program) {
clStatus = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL); if (clCallSuccess(clStatus)) {
// Create the OpenCL kernel
cl_kernel kernel = clCreateKernel(program, "saxpy_kernel", &clStatus); if (clCallSuccess(clStatus) && kernel) {
float alpha = 2.5;
// Set the arguments of the kernel
clStatus = clSetKernelArg(kernel, 0, sizeof(float), (void*)&alpha); if (clCallSuccess(clStatus)) {
clStatus = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&A_clmem); if (clCallSuccess(clStatus)) {
clStatus = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*)&B_clmem); if (clCallSuccess(clStatus)) {
clStatus = clSetKernelArg(kernel, 3, sizeof(cl_mem), (void*)&C_clmem); if (clCallSuccess(clStatus)) {
// Execute the OpenCL kernel on the list
cl_event event;
size_t global_size = VECTOR_SIZE; // Process the entire lists
size_t local_size = 512; // Process one item at a time
//clStatus = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &global_size, &local_size, 0, NULL, &event);
clStatus = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &global_size, NULL, 0, NULL, &event); if (clCallSuccess(clStatus)) {
clStatus = clWaitForEvents(1, &event); if (clCallSuccess(clStatus)) {
//measure duration
cl_ulong time_start;
cl_ulong time_end;
clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_START, sizeof(time_start), &time_start, NULL);
clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_END, sizeof(time_end), &time_end, NULL);
microSeconds = (float)(time_end - time_start) / 1000.0f;
std::clog << "\nOpenCl benchmarking time: " << microSeconds << " microseconds \n";
std::clog << "\n\t*****************************\n\n";
}
// Read the cl memory C_clmem on device to the host variable C
clCallSuccess(clEnqueueReadBuffer(command_queue, C_clmem, CL_TRUE, 0, VECTOR_SIZE * sizeof(float), C, 0, NULL, NULL));
// Clean up and wait for all the comands to complete.
clCallSuccess(clFlush(command_queue));
clCallSuccess(clFinish(command_queue));
} //Kernel
}}}} //SetKErnelArg
// Finally release all OpenCL allocated objects and host buffers.
clCallSuccess(clReleaseKernel(kernel)); }
} //BuildProgram
clCallSuccess(clReleaseProgram(program)); }
} } //EnqueueWriteBuffer
clCallSuccess(clReleaseMemObject(C_clmem)); }
clCallSuccess(clReleaseMemObject(B_clmem)); }
clCallSuccess(clReleaseMemObject(A_clmem)); }
free(C); }
free(B); }
free(A); }
return microSeconds;
}
/*
struct _dev_info {
cl_platform_id platfID;
cl_device_id devID;
};
typedef struct _dev_info dev_info;
*/
cl_device_id cl_GetBestDevice(void)
{
cl_int err;
cl_uint numPlatforms, numDevices;
cl_platform_id platfIDs[10];
cl_device_id devIDsAll[10];
int countGPUs = 0;
cl_device_id best_device = NULL;
float best_perf = 100000000.;
if (clCallSuccess(clGetPlatformIDs(10, platfIDs, &numPlatforms)))
{
std::clog << "OpenCL platforms detected: " << numPlatforms << std::endl;
for (unsigned int i = 0; i < numPlatforms; i++)
{
std::clog << "PlatformInfo for platform no." << (i + 1) << std::endl;
const int SZ_INFO = 1024;
char info[SZ_INFO];
size_t sz;
if (clCallSuccess(clGetPlatformInfo(platfIDs[i], CL_PLATFORM_NAME, SZ_INFO, info, &sz)))
std::clog << " - - Name: " << info << std::endl;
if (clCallSuccess(clGetPlatformInfo(platfIDs[i], CL_PLATFORM_VENDOR, SZ_INFO, info, &sz)))
std::clog << " - - Vendor: " << info << std::endl;
if (clCallSuccess(clGetPlatformInfo(platfIDs[i], CL_PLATFORM_PROFILE, SZ_INFO, info, &sz)))
std::clog << " - - Profile: " << info << std::endl;
if (clCallSuccess(clGetPlatformInfo(platfIDs[i], CL_PLATFORM_VERSION, SZ_INFO, info, &sz)))
std::clog << " - - Version: " << info << std::endl;
if (clCallSuccess(clGetPlatformInfo(platfIDs[i], CL_PLATFORM_EXTENSIONS, SZ_INFO, info, &sz)))
std::clog << " - - Extensions: " << info << std::endl;
if (clCallSuccess(clGetDeviceIDs(platfIDs[i], CL_DEVICE_TYPE_ALL, 10, devIDsAll, &numDevices)))
{
cl_context_properties cProperties[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)(platfIDs[i]), 0 };
cl_command_queue_properties qProperties[] = { CL_QUEUE_PROPERTIES, CL_QUEUE_PROFILING_ENABLE, 0 };
for (unsigned int ii = 0; ii < numDevices; ii++)
{
cl_uint val;
cl_ulong memsz;
cl_device_type dt;
size_t mws;
std::clog << " >> DeviceInfo for device no." << (ii + 1) << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_NAME, SZ_INFO, info, &sz)))
std::clog << "\t - Name: " << info << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_VENDOR, SZ_INFO, info, &sz)))
std::clog << "\t - Vendor: " << info << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_VERSION, SZ_INFO, info, &sz)))
std::clog << "\t - Version: " << info << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_TYPE, sizeof(dt), &dt, &sz)))
{
std::clog << "\t - Type: ";
switch (dt)
{
case CL_DEVICE_TYPE_CPU: std::clog << "CPU"; break;
case CL_DEVICE_TYPE_GPU: std::clog << "GPU"; break;
case CL_DEVICE_TYPE_ACCELERATOR: std::clog << "Accelerator"; break;
case CL_DEVICE_TYPE_DEFAULT: std::clog << "Default"; break;
default: std::clog << "ERROR";
}
std::clog << std::endl;
}
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(memsz), &memsz, &sz)))
std::clog << "\t - Memory: " << (memsz / 1024 / 1024) << " MB" << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(val), &val, &sz)))
std::clog << "\t - Max Frequency: " << val << " MHz" << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(val), &val, &sz)))
std::clog << "\t - Compute units: " << val << std::endl;
if (clCallSuccess(clGetDeviceInfo(devIDsAll[ii], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(mws), &mws, &sz)))
std::clog << "\t - Max workgroup size: " << mws << std::endl;
// Create an OpenCL context
cl_context context = clCreateContext(NULL, 1, devIDsAll+ii, NULL, NULL, &err);
if (clCallSuccess(err) && context)
{
// Create a command queue
cl_command_queue command_queue = clCreateCommandQueueWithProperties(context, devIDsAll[ii], qProperties, &err);
if (clCallSuccess(err) && command_queue)
{
float perf = cl_BenchmarkDevice(context, command_queue, devIDsAll[ii]);
if ((perf > 0) && (perf < best_perf))
{
best_perf = perf;
best_device = devIDsAll[ii];
}
clCallSuccess(clReleaseCommandQueue(command_queue));
}
clCallSuccess(clReleaseContext(context));
}
}
}
}
}
return best_device;
}