C++ 如何优化Cuda内核性能?

C++ 如何优化Cuda内核性能?,c++,opengl,cuda,C++,Opengl,Cuda,我正在构建一个粒子系统,在计算粒子位置的cuda内核性能方面遇到了困难 __global__ void updateParticle(const int num_particles, const double time, const double gravity, GLfloat* device_particleCoordinates, GLfloat* device_particleStartCoordinates,

我正在构建一个粒子系统,在计算粒子位置的cuda内核性能方面遇到了困难

__global__
void updateParticle(const int num_particles, const double time, const double gravity,
                    GLfloat* device_particleCoordinates, GLfloat* device_particleStartCoordinates,
                    GLfloat* device_particleAcceleration, GLint* device_particleCreatedTime)
{
    int threadId = threadIdx.x + blockIdx.x * blockDim.x;

    if (threadId < num_particles)
    {
        int particleLifetime = (time - device_particleCreatedTime[threadId]) / 1000;

        double distanceX = 0.5 * device_particleAcceleration[threadId * 2 + 0] * (particleLifetime * particleLifetime) / 5000.0;
        double distanceY = 0.5 * device_particleAcceleration[threadId * 2 + 1] * (particleLifetime * particleLifetime) / 5000.0;

        device_particleCoordinates[threadId * 2 + 0] = device_particleStartCoordinates[threadId * 2 + 0] + distanceX;
        device_particleCoordinates[threadId * 2 + 1] = device_particleStartCoordinates[threadId * 2 + 1] + distanceY;
    }
}
\u全局__
void updateParticle(常量int num_粒子、常量加倍时间、常量加倍重力、,
GLfloat*设备粒子坐标,GLfloat*设备粒子坐标,
GLfloat*设备\粒子过滤加速,GLint*设备\粒子创建时间)
{
int-threadId=threadIdx.x+blockIdx.x*blockDim.x;
if(线程ID<粒子数)
{
int particleLifetime=(时间-设备_particleCreatedTime[threadId])/1000;
双距离X=0.5*设备\粒子加速[threadId*2+0]*(粒子寿命时间*粒子寿命时间)/5000.0;
双距离Y=0.5*设备\粒子加速[threadId*2+1]*(粒子寿命时间*粒子寿命时间)/5000.0;
设备粒子坐标[threadId*2+0]=设备粒子坐标[threadId*2+0]+距离X;
设备粒子坐标[threadId*2+1]=设备粒子坐标[threadId*2+1]+距离;
}
}
内核的调用方式如下:

int blockSize = 32;
int nBlocks = maxParticleCount / 32 + 1;
updateParticle << <nBlocks, blockSize >> >(particles.size(), time, gravity, device_particleCoordinates,
                                            device_particleStartCoordinates, device_particleAcceleration, device_particleCreatedTime);

glDrawArrays(GL_POINTS, 0, particles.size());

HANDLE_ERROR(cudaMemcpy(particleCoordinatesFlat.data(), device_particleCoordinates, particles.size() * 2 * sizeof(GLfloat), cudaMemcpyDeviceToHost));
int blockSize=32;
int nBlocks=maxparticlecont/32+1;
更新粒子>(particles.size()、时间、重力、设备粒子坐标、,
设备\u粒子起始坐标、设备\u粒子加速、设备\u粒子创建时间);
gldrawArray(GL_点,0,particles.size());
处理错误(cudaMemcpy(particleCoordinationsFlat.data(),设备(particleCoordinations,particles.size()*2*sizeof(GLfloat),cudaMemcpyDeviceToHost));
device_particleCoordinates链接到OpenGL缓冲区,以便直接修改坐标


性能不是很好,我认为这是由于内核调用。是否存在任何可能影响性能的明显缺陷?

正如评论中所建议的,此内核可能不是您认为的性能限制。至少,你没有提供任何数据支持这个想法。但是,仍然可以提出一些建议,以改进内核的运行时

  • 我假设
    GLfloat
    等同于
    float
    。在这种情况下,特别是由于该内核(
    设备粒子坐标
    )的主要输出是
    浮点
    量,因此以
    双精度
    进行的任何中间计算是否能提供多大的好处值得怀疑。您可以尝试将所有操作转换为
    float
    算术

  • GPU代码中的除法可能很昂贵。对于浮点运算,被常数除法可以用常数倒数的乘法来代替

  • 您的加载和存储操作正在加载备用位置。使用矢量加载/存储可以提高效率。如注释所示,这是对基础数据对齐的假设

  • 下面是一个经过修改的内核(未经测试)示例,演示了这些想法:

    __global__
    void updateParticle1(const int num_particles, const double time, const double gravity,
                        GLfloat* device_particleCoordinates, GLfloat* device_particleStartCoordinates,
                        GLfloat* device_particleAcceleration, GLint* device_particleCreatedTime)
    {
        int threadId = threadIdx.x + blockIdx.x * blockDim.x;
    
        if (threadId < num_particles)
        {
            float particleLifetime = (int)((((float)time) - (float)device_particleCreatedTime[threadId]) * (0.001f));
            float2 dpA = *(reinterpret_cast<float2 *>(device_particleAcceleration)+threadId);
            float spl2 = 0.0001f * particleLifetime*particleLifetime;
            float distanceX = dpA.x * spl2;
            float distanceY = dpA.y * spl2;
            float2 dpC = *(reinterpret_cast<float2 *>(device_particleStartCoordinates)+threadId);
            dpC.x += distanceX;
            dpC.y += distanceY;
            *(reinterpret_cast<float2 *>(device_particleCoordinates)+threadId) = dpC;
        }
    }
    
    常量装饰指针__restrict\uuu
    updateParticle2
    )似乎没有为这个测试用例提供任何额外的好处。计算每个线程4个粒子(
    updateParticle3
    )而不是1个粒子似乎对处理时间没有显著影响


    特斯拉P100、CUDA 10.0、CentOS 7.5

    正如评论中已经提到的,该内核可能不是您认为的性能限制。至少,你没有提供任何数据支持这个想法。但是,仍然可以提出一些建议,以改进内核的运行时

  • 我假设
    GLfloat
    等同于
    float
    。在这种情况下,特别是由于该内核(
    设备粒子坐标
    )的主要输出是
    浮点
    量,因此以
    双精度
    进行的任何中间计算是否能提供多大的好处值得怀疑。您可以尝试将所有操作转换为
    float
    算术

  • GPU代码中的除法可能很昂贵。对于浮点运算,被常数除法可以用常数倒数的乘法来代替

  • 您的加载和存储操作正在加载备用位置。使用矢量加载/存储可以提高效率。如注释所示,这是对基础数据对齐的假设

  • 下面是一个经过修改的内核(未经测试)示例,演示了这些想法:

    __global__
    void updateParticle1(const int num_particles, const double time, const double gravity,
                        GLfloat* device_particleCoordinates, GLfloat* device_particleStartCoordinates,
                        GLfloat* device_particleAcceleration, GLint* device_particleCreatedTime)
    {
        int threadId = threadIdx.x + blockIdx.x * blockDim.x;
    
        if (threadId < num_particles)
        {
            float particleLifetime = (int)((((float)time) - (float)device_particleCreatedTime[threadId]) * (0.001f));
            float2 dpA = *(reinterpret_cast<float2 *>(device_particleAcceleration)+threadId);
            float spl2 = 0.0001f * particleLifetime*particleLifetime;
            float distanceX = dpA.x * spl2;
            float distanceY = dpA.y * spl2;
            float2 dpC = *(reinterpret_cast<float2 *>(device_particleStartCoordinates)+threadId);
            dpC.x += distanceX;
            dpC.y += distanceY;
            *(reinterpret_cast<float2 *>(device_particleCoordinates)+threadId) = dpC;
        }
    }
    
    常量装饰指针__restrict\uuu
    updateParticle2
    )似乎没有为这个测试用例提供任何额外的好处。计算每个线程4个粒子(
    updateParticle3
    )而不是1个粒子似乎对处理时间没有显著影响


    特斯拉P100、CUDA 10.0、CentOS 7.5

    除了罗伯特·克罗维拉的建议外,还应考虑:

    • 每个内核线程处理更多的元素。你看,将线程设置为运行需要一些时间——读取参数、初始化变量(在你的例子中可能没有这么多)等等。当然,不是每个线程都处理连续的元素,而是在扭曲过程中有连续的通道来处理连续的元素
    • 在所有指针参数上使用
      \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
      ——假设它们指向不同的内存区域。nvcc支持的
      \uuuuu restrict\uuuu
      关键字允许编译器进行各种非常有用的优化,否则它无法进行这些优化。更多关于为什么<>代码>限制符< /COD>是有用的(在C++中一般)参见:< /P>


    除了Robert Crovella的建议外,还应考虑:

    • 每个内核线程处理更多的元素。你看,设置一个线程运行需要一些时间