使用CPU上的OpenCL将一个阵列复制到另一个阵列比C ++代码慢得多

时间:2013-11-20 07:33:53

标签: c++ arrays performance opencl

我比较了在CPU上运行的OpenCL代码的性能,它只是将一个2D数组中的数据复制到另一个2D数组中的纯C ++代码,它执行相同的操作。我在OpenCL代码中使用了一个工作组来进行公平的比较。我使用了英特尔的OpenCL驱动程序和英特尔编译器。 OpenCL代码比CPU代码慢大约5倍。编译器为复制循环提供以下消息:

loop was transformed to memset or memcpy.

有关如何使用C ++代码加快OpenCL代码速度的任何建议吗?

由于

OpenCL主机代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <cmath>
#include <ctime>
#include <CL/cl.hpp>

int main(int argc, char **argv)
{
    // Create the two input vectors
    const int N = 8192;
    double *in = new double[N*N]; 
    double *out = new double[N*N];

    for(int i = 0; i < N; i++)
        for (int j=0; j < N; j++) {
            in[i*N + j] = i + j;
            out[i*N + j] = 0.;
    }


    double time;
    std::clock_t start;
    int niter = 100;

    cl_int cl_err;

    std::vector<cl::Platform> platforms;
    cl_err = cl::Platform::get(&platforms);

    std::vector<cl::Device> devices;
    cl_err = platforms.at(1).getDevices(CL_DEVICE_TYPE_CPU,
                                        &devices);

    cl_context_properties context_properties[3] = {CL_CONTEXT_PLATFORM,
                                    (cl_context_properties)(platforms.at(1)()),
                                                   0};
    cl::Context context = cl::Context(devices, 
                                      context_properties, 
                                      NULL, NULL, &cl_err);

    cl::Buffer buffer_in = cl::Buffer(context, 
                                      CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
                                      N*N*sizeof(double), 
                                      in, &cl_err);

    cl::Buffer buffer_out = cl::Buffer(context, 
                                       CL_MEM_USE_HOST_PTR | CL_MEM_WRITE_ONLY, 
                                       N*N*sizeof(double),
                                       out, &cl_err);

    cl::CommandQueue queue = cl::CommandQueue(context, devices.at(0), 0, &cl_err);

    std::ifstream sourceFile("vector_copy.cl");
    std::string sourceCode((std::istreambuf_iterator<char>(sourceFile)),
                            std::istreambuf_iterator<char>());
    cl::Program::Sources source(1, std::make_pair(sourceCode.c_str(),
                                sourceCode.length()+1));

    cl::Program program(context, source, &cl_err);

    cl_err = program.build(devices, NULL, NULL, NULL);

    cl::Kernel kernel(program, "vector_copy", &cl_err);

    cl_err = kernel.setArg(0, buffer_in); 
    cl_err = kernel.setArg(1, buffer_out);
    cl_err = kernel.setArg(2, N);

    cl::NDRange global(N);
    cl::NDRange local(N);

    start = std::clock();
    for (int n=0; n < niter; n++) {
        cl_err = queue.enqueueNDRangeKernel(kernel,
                                            cl::NullRange,
                                            global,
                                            local,
                                            NULL, NULL);

        cl_err = queue.finish();
    }

    time =  (std::clock() - start)/(double)CLOCKS_PER_SEC;
    std::cout << "Time/iteration OpenCL (s) = " << time/(double)niter << std::endl;

    return(0);
}

OpenCL内核代码:

__kernel void vector_copy(__global const double* restrict in, 
                          __global double* restrict out,
                         const int N) 
{

    int i = get_global_id(0);
    int j;

    for (j=0; j<N; j++) {
        out[j + N*i] = in[j + N*i];
    }

}

C ++代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <cmath>
#include <ctime>

const int N = 8192;

int main(int argc, char **argv)
{
    double *in = new double[N*N]; 
    double *out = new double[N*N];
    // Create the two input vectors
    for(int i = 0; i < N; i++)
        for (int j=0; j < N; j++) {
            in[j + N*i] = i + j;
            out[j + N*i] = 0.;
    }


    std::clock_t start;
    int niter = 100;

    start = std::clock();
    for (int n=0; n < niter; n++) {
        for (int i=0; i<N; i++)
            for (int j=0; j<N; j++) {
                out[j + N*i] = in[j + N*i];
            }

    }

    double time =  (std::clock() - start)/(double)CLOCKS_PER_SEC;
    std::cout << "Time/iteration C = " << time/(double)niter << std::endl;

    return(0);
}

2 个答案:

答案 0 :(得分:4)

英特尔OpenCL编译器能够跨工作组进行矢量化。基本上,单个函数作为示例在不同的SSE寄存器中同时运行8个线程。

您的特定内核不会这样做。但这并不重要。我使用Visual Studio 2010和最新的Intel OpenCL应用程序测试了您的程序。我被迫将N从8192减少到4096,因为我使用集成GPU将最大OpenCL缓冲区大小减少到128MB,即使只使用了CPU。

我的结果:你的OpenCL内核给了我大约6956MB / s的带宽。一个简单改变的内核(调用N * N作为全局大小,NULL作为本地大小调用,因为如果我们根本不关心本地内存那么对于CPU,我们应该保留未定义的内容。)

__kernel void vector_copy2(__global const double* restrict in, 
                      __global double* restrict out) 
{
  int i = get_global_id(0);
  out[i] = in[i];
}

给出了相同的结果(7006MB / s)。这个内核实际上是跨线程进行矢量化的,可以使用Intel OpenCL内核编译器进行验证。它为一个多线程(如4)生成一个内核,为单个线程生成一个内核。然后它只运行矢量化内核,直到它必须运行最后几个工作项的单线程内核。

C ++代码给出了6494MB / s。所以它非常符合要求。我认为ICC甚至不可能使它快5倍。

我在你的代码中注意到你有平台.at(1),你计算机上的平台0是什么?

请记住,如果您根本不关心本地内存(您不在内核中调用get_local_id),则应将enqueueNDRange的本地大小视为一个简单的魔术参数。将其保留为NULL或尝试查找产生最快结果的值。

答案 1 :(得分:2)

OpenCL代码,即使经过优化,仍然会执行副本1by1(工作项目的工作项)。因为OpenCL编译器只允许在每个工作项的基础上进行优化。虽然编译器可能会将C ++案例优化为memcpy()调用(正如编译器告诉你的那样)。

如果禁用编译器优化,它将在GPU中执行得更快。

BTW有这样的原因吗?为此,您在C ++中使用memcpy(),在OpenCL中使用 clEnqueueCopyBuffer() 。我认为后者是你应该使用的。