2013-05-10 11 views
3

Прямо сейчас я запрограммировал несколько параллельных алгоритмов на одном графическом процессоре, но все они имеют ту же проблему, когда я пытаюсь выполнить их на нескольких графических процессорах (например, 3). Проблема в том, что код, выполняемый на одном графическом процессоре, выполняет ровно столько же времени на 3 графических процессорах (не быстрее). Я попытался выполнить с большим количеством данных, попробовал выполнить различные задачи, ничего не помогло. Наконец, я закончил тем, что пытался выполнить самую легкую задачу, такую ​​как сумма элементов, и до сих пор получил эту ужасную ошибку. Вот почему я не считаю, что это проблема конкретного алгоритма, и я чувствую, что в моем коде есть ошибка (или даже мой подход к распараллеливанию кода на нескольких графических процессорах).Запуск ядра OpenCL на нескольких графических процессорах?

Вот файл заголовка для моего класса Parallel.cpp:

#ifndef PARALLEL_H 
#define PARALLEL_H 

#define __NO_STD_VECTOR // Use cl::vector and cl::string and 
#define __NO_STD_STRING // not STL versions, more on this later 
#include <CL/cl.h> 

class Parallel 
{ 
    public: 
     Parallel(); 
     int executeAttachVectorsKernel(int*, int*, int*, int); 
     static void getMaxWorkGroupSize(int*, int*, int*); 
     virtual ~Parallel(); 
    protected: 
    private: 
     char* file_contents(const char*, int*); 
     void getShortInfo(cl_device_id); 
     int init(void); 
     cl_platform_id platform; 
     cl_device_id* devices; 
     cl_uint num_devices; 
     cl_command_queue* queues; 
     int* WGSizes; 
     int* WGNumbers; 
     cl_context context; 
     cl_program program; 
     cl_kernel kernel; 
     cl_mem input1; 
     cl_mem input2; 
     cl_mem output; 
}; 

#endif // PARALLEL_H 

Вот метод инициализации INIT:

int Parallel::init() { 
cl_int err; 

//Connect to the first platfrom 
err = clGetPlatformIDs(1, &platform, NULL); 
if (err != CL_SUCCESS) { 
    cerr << "Error occured while executing clGetPlatformIDs" << endl; 
    return EXIT_FAILURE; 
} 

//Get devices number 
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL, &num_devices); 
if (err != CL_SUCCESS) { 
    cerr << "Error: Failed to create a device group:" << endl; 
    return EXIT_FAILURE; 
} 

cout << "NUM DEVICES =" << num_devices << endl; 

devices = new cl_device_id[num_devices]; 
//Get all the GPU devices 
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, num_devices, devices, NULL); 

//Create one context for all the devices 
context = clCreateContext(NULL, num_devices, devices, NULL, NULL, &err); 
if (!context) { 
    cerr << "Error: Failed to create a compute context!" << endl; 
    return EXIT_FAILURE; 
} 

queues = new cl_command_queue[num_devices]; 
WGNumbers = new int[num_devices]; 
WGSizes = new int[num_devices]; 


for(int i = 0; i < num_devices; i++) { 
    //Create a command queue for every device 
    queues[i] = clCreateCommandQueue(context, devices[i], 0, &err); 
    if (!queues[i]) { 
     cerr << "Error: Failed to create a command commands!" << endl; 
     return EXIT_FAILURE; 
    } 

    cl_ulong temp; 
    clGetDeviceInfo(devices[i], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(temp), &temp, NULL); 
    WGSizes[i] = (int)temp; 

    clGetDeviceInfo(devices[i], CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(temp), &temp, NULL); 
    WGNumbers[i] = (int)temp; 
} 

//Translate kernel code into chars 
int pl; 
size_t program_length; 
string path = "./kernel/kernel_av.cl"; 

char* cSourceCL = file_contents(path.c_str(), &pl); 
program_length = (size_t)pl; 

//Create a program 
program = clCreateProgramWithSource(context, 1, 
        (const char **) &cSourceCL, &program_length, &err); 

if (!program) { 
    cerr << "Error: Failed to create compute program!" << endl; 
    return EXIT_FAILURE; 
} 

//Create an executable 
err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL); 
if (err != CL_SUCCESS) 
{ 
    size_t len; 
    char buffer[2048]; 

    cerr << "Error: Failed to build program executable!" << endl; 
    exit(1); 
} 

// Create the compute kernel in the program 
kernel = clCreateKernel(program, "calculate2dim", &err); 
if (err != CL_SUCCESS) 
{ 
    cerr << "Error: Failed to create compute kernel!" << endl; 
    exit(1); 
} 
} 

Метод, который выполняет ядро ​​здесь:

int Parallel::executeAttachVectorsKernel(int* data1, int* data2, int* results, int vectors_num) { 

cl_int err; 
size_t global; // global domain size for our calculation 
size_t local; // local domain size for our calculation 

int partition = vectors_num/num_devices; 
unsigned int count = partition; 
input1 = clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(int) * count, NULL, NULL); 
input2 = clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(int) * count, NULL, NULL); 
output = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(int) * count, NULL, NULL); 
if (!input1 || !input2 || !output) { 
    cerr << "Error: Failed to allocate device memory!" << endl; 
    exit(1); 
} 

int** data1_apart = new int*[num_devices]; 
int** data2_apart = new int*[num_devices]; 
int** results_apart = new int*[num_devices]; 

for(int i = 0; i < num_devices; i++) { 
    cout << "Executing parallel part on GPU " << i + 1 << endl; 
    cout << "Partition size = " << partition << endl; 
    data1_apart[i] = new int[partition]; 
    data2_apart[i] = new int[partition]; 
    results_apart[i] = new int[partition]; 

    for(int j = i*partition, k = 0; k < partition; j++, k++) { 
     data1_apart[i][k] = data1[j]; 
     data2_apart[i][k] = data2[j]; 
    } 

    //Transfer the input vector into device memory 
    err = clEnqueueWriteBuffer(queues[i], input1, 
           CL_TRUE, 0, sizeof(int) * count, 
           data1_apart[i], 0, NULL, NULL); 

    err = clEnqueueWriteBuffer(queues[i], input2, 
           CL_TRUE, 0, sizeof(int) * count, 
           data2_apart[i], 0, NULL, NULL); 

    if (err != CL_SUCCESS) 
    { 
     cerr << "Error: Failed to write to source array!" << endl; 
     exit(1); 
    } 

    int parameter4 = count/WGNumbers[i]; 

    //Set the arguments to the compute kernel 
    err = 0; 
    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &input1); 
    err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &input2); 
    err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &output); 
    err |= clSetKernelArg(kernel, 3, sizeof(int), &parameter4); 
    if (err != CL_SUCCESS) 
    { 
     cerr << "Error: Failed to set kernel arguments! " << err << endl; 
     exit(1); 
    } 

    global = WGNumbers[i]; 
    local = WGSizes[i]; 

    if(local > global) { 
     local = global; 
    } 
    cout << "global = " << global << " local = " << local << endl; 

    err = clEnqueueNDRangeKernel(queues[i], kernel, 
           1, NULL, &global, &local, 
           0, NULL, NULL); 
    if (err) 
    { 
     cerr << "Error: Failed to execute kernel!" << endl; 
     return EXIT_FAILURE; 
    } 
} 

for(int i = 0; i < num_devices; i++) { 
    //Wait for all commands to complete 
    clFinish(queues[i]); 

    //Read back the results from the device to verify the output 

    err = clEnqueueReadBuffer(queues[i], output, 
           CL_TRUE, 0, sizeof(int) * count, 
           results_apart[i], 0, NULL, NULL); 
    if (err != CL_SUCCESS) 
    { 
     cerr << "Error: Failed to read output array! " << err << endl; 
     exit(1); 
    } 

    for(int j = 0; j < partition; j++) { 
     results[i*partition + j] = results_apart[i][j]; 
    } 

    delete [] data1_apart[i]; 
    delete [] data2_apart[i]; 
    delete [] results_apart[i]; 
} 

clReleaseMemObject(input1); 
clReleaseMemObject(input2); 
clReleaseMemObject(output); 
delete [] data1_apart; 
delete [] data2_apart; 
} 

Прежде чем отправлять этот вопрос в stackoverflow, я боролся с этой проблемой в течение 2-3 недель, и теперь я действительно кто-то помог, поэтому я буду очень признателен за любые мысли и ответы!

ответ

1

Все ваши устройства работают на одних и тех же буферах. Данные будут перемещаться между устройствами при выполнении ядер. Без правильной синхронизации результаты будут неопределенными.

Если возможно, рассмотрите возможность выделения отдельного набора буферов для каждого устройства.

+0

Это то, что я делаю прямо сейчас и почти мгновенно (разница ~ 1 мс) для тяжелых нагрузок (~ 130 мс работы) –

2

Вот что я думаю, что происходит. Вы вызываете clEnqueueNDRangeKernel один раз для каждого участвующего opencl-устройства. На этом этапе ни одно из ядер не запускалось, потому что clFlush не был вызван. Затем вы создаете clFinish для каждой очереди. Первый clFinish вызов вызывает запуск первой рабочей группы. Он также ждет его завершения. После завершения первой рабочей группы clFinish возвращает управление вашему приложению. Затем ваше приложение вызывает clFinish для следующей очереди. Это вызывает запуск второго рабочего раствора, а также ждет его завершения. Таким образом, работа выполняется последовательно. Решение может быть таким же простым, как вызов clFush сразу после каждого вызова clEnqueueNDRangeKernel. Так поступает моя система AMD. Вскоре я отправлю рабочий пример.

+0

Спасибо, я попробую! Если возможно, просьба дать больше комментариев по этому подходу! – Vladimir

+0

Вот рабочий пример [ссылка] http://notabs.org/blcutil/wip/blcutil_devel-018.7z Вот как запустить его на одном устройстве, затем в другом, то и в другом: [link] http://notabs.org/blcutil/wip/sample-output.htm Опция командной строки -opencl сделает список программ opencl. Опция командной строки -opencl = позволяет выбрать одно или несколько перечисленных устройств для использования. – ScottD

+0

Да, я думаю, это правильно.Спецификация OpenCL очень специфична: «Обратите внимание, что когда обратный вызов (или другой код) вставляет команды в очередь команд, команды не требуются для начала выполнения до тех пор, пока очередь не будет сброшена». – doug65536

0

Какие графические процессоры вы используете? У меня есть GTX590, который появляется на двух устройствах GPU. Когда я пытался работать на обоих устройствах, он, казалось, дождался завершения каждого устройства до того, как он переместился на следующий (хотя это и не предполагалось). Я не знаю, исправила ли Nvidia это.

Чтение некоторых сообщений, которые я думаю на сайте Nvidia в то время, когда я что-то читал о Nvidia, предлагая создавать отдельные контексты для каждого устройства и запускать их в разных потоках. Это то, что я сделал, и он отлично работает. Для этого я использовал pthreads (или SDL_threads). Его очень легко настроить.

Смежные вопросы