C++ 如何在OpenGL中测量峰值内存带宽?

C++ 如何在OpenGL中测量峰值内存带宽?,c++,opengl,glsl,nvidia,C++,Opengl,Glsl,Nvidia,为了了解我应该期望的速度,我一直在尝试对全局内存和着色器之间的传输进行基准测试,而不是依赖GPU规格表。但是我不能接近理论上的最大值。事实上,我被淘汰了50倍 我用的是GTX泰坦X,它是。Linux x64驱动程序352.21 我找到了一个CUDA基准测试,它提供了约240–250GB/s的速度(这比我预期的要高) 我正试图精确匹配它们对着色器的操作。我尝试过顶点着色器、计算着色器、通过访问缓冲区对象,以及使用floats、vec4s、着色器内的循环(使用工作组内的合并寻址)和各种计时方法。我被

为了了解我应该期望的速度,我一直在尝试对全局内存和着色器之间的传输进行基准测试,而不是依赖GPU规格表。但是我不能接近理论上的最大值。事实上,我被淘汰了50倍

我用的是GTX泰坦X,它是。Linux x64驱动程序352.21

我找到了一个CUDA基准测试,它提供了约240–250GB/s的速度(这比我预期的要高)

我正试图精确匹配它们对着色器的操作。我尝试过顶点着色器、计算着色器、通过访问缓冲区对象,以及使用
float
s、
vec4
s、着色器内的循环(使用工作组内的合并寻址)和各种计时方法。我被限制在~7GB/s(请参阅下面的更新)

为什么GL的速度要慢得多?我做错了什么吗?如果是,应该怎么做?

这是我的MWE,有三种方法(1.使用图像加载存储的顶点着色器,2.使用无绑定图形的顶点着色器,3.使用无绑定图形计算着色器):

一些注意事项:

  • 使用无绑定图形的计算着色器似乎不会写入任何内容(注释掉的断言失败),或者至少不会使用
    glMapBuffer
    检索数据,即使速度与其他方法匹配。在计算着色器中使用image_load_store可以工作,并提供与顶点着色器相同的速度(尽管我认为这可能是一个太多的排列)
  • glDispatchCompute
    之前调用
    glMemoryBarrier(GL\u ALL\u BARRIER\u BITS)
    会导致驱动程序崩溃
  • 注释掉三个
    glBufferData(GL\u TEXTURE\u BUFFER,bufferSize,dat,GL\u STATIC\u DRAW),将前两个测试的速度提高到17GB/s,计算着色器的速度猛增到292GB/s,这非常接近我想要的速度,但由于第1点的原因,这是不可信的
  • 有时(!available)会挂起很长时间(当我厌倦了等待时,ctrl-c显示它仍在循环中)
  • 以下是CUDA代码供参考:

    //http://www.ks.uiuc.edu/Research/vmd/doxygen/CUDABench_8cu-source.html
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <cuda.h>
    
    #define CUERR { cudaError_t err; \
        if ((err = cudaGetLastError()) != cudaSuccess) { \
        printf("CUDA error: %s, %s line %d\n", cudaGetErrorString(err), __FILE__, __LINE__); \
        return -1; }}
    
    //
    // GPU device global memory bandwidth benchmark
    //
    template <class T>
    __global__ void gpuglobmemcpybw(T *dest, const T *src) {
        const unsigned int idx = threadIdx.x + blockIdx.x * blockDim.x;
        dest[idx] = src[idx];
    }
    
    template <class T>
    __global__ void gpuglobmemsetbw(T *dest, const T val) {
        int idx = threadIdx.x + blockIdx.x * blockDim.x;
        dest[idx] = val;
    }
    
    typedef float4 datatype;
    
    static int cudaglobmembw(int cudadev, double *gpumemsetgbsec, double *gpumemcpygbsec) {
        int i;
        int len = 1 << 22; // one thread per data element
        int loops = 500;
        datatype *src, *dest;
        datatype val=make_float4(1.0f, 1.0f, 1.0f, 1.0f);
    
        // initialize to zero for starters
        float memsettime = 0.0f;
        float memcpytime = 0.0f;
        *gpumemsetgbsec = 0.0;
        *gpumemcpygbsec = 0.0;
    
        // attach to the selected device
        cudaError_t rc;
        rc = cudaSetDevice(cudadev);
        if (rc != cudaSuccess) {
            #if CUDART_VERSION >= 2010
            rc = cudaGetLastError(); // query last error and reset error state
            if (rc != cudaErrorSetOnActiveProcess)
            return -1; // abort and return an error
            #else
            cudaGetLastError(); // just ignore and reset error state, since older CUDA
            // revs don't have a cudaErrorSetOnActiveProcess enum
            #endif
        }
    
        cudaMalloc((void **) &src, sizeof(datatype)*len);
        CUERR
        cudaMalloc((void **) &dest, sizeof(datatype)*len);
        CUERR
    
        dim3 BSz(256, 1, 1);
        dim3 GSz(len / (BSz.x * BSz.y * BSz.z), 1, 1); 
    
        // do a warm-up pass
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(src, val);
        CUERR
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(dest, val);
        CUERR
        gpuglobmemcpybw<datatype><<< GSz, BSz >>>(dest, src);
        CUERR
    
        cudaEvent_t start, end;
        cudaEventCreate(&start);
        cudaEventCreate(&end);
    
        // execute the memset kernel
        cudaEventRecord(start, 0);
        for (i=0; i<loops; i++) {
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(dest, val);
        }
        CUERR
        cudaEventRecord(end, 0);
        CUERR
        cudaEventSynchronize(start);
        CUERR
        cudaEventSynchronize(end);
        CUERR
        cudaEventElapsedTime(&memsettime, start, end);
        CUERR
    
        // execute the memcpy kernel
        cudaEventRecord(start, 0);
        for (i=0; i<loops; i++) {
        gpuglobmemcpybw<datatype><<< GSz, BSz >>>(dest, src);
        }
        cudaEventRecord(end, 0);
        CUERR
        cudaEventSynchronize(start);
        CUERR
        cudaEventSynchronize(end);
        CUERR
        cudaEventElapsedTime(&memcpytime, start, end);
        CUERR
    
        cudaEventDestroy(start);
        CUERR
        cudaEventDestroy(end);
        CUERR
    
        *gpumemsetgbsec = (len * sizeof(datatype) / (1024.0 * 1024.0)) / (memsettime / loops);
        *gpumemcpygbsec = (2 * len * sizeof(datatype) / (1024.0 * 1024.0)) / (memcpytime / loops);
        cudaFree(dest);
        cudaFree(src);
        CUERR
    
        return 0;
    }
    
    int main()
    {
        double a, b;
        cudaglobmembw(0, &a, &b);
        printf("%f %f\n", (float)a, (float)b);
        return 0;
    }
    
    我现在通过使用图像加载存储或无绑定图形的计算着色器获得270–290GB/s现在我的问题包括

    • 假设每次测试都有缓冲区,并且计算着色器又好又快,为什么顶点着色器版本仍然如此缓慢
    • 如果没有无绑定图形扩展,普通OpenGL用户应该如何将数据放入视频内存中(实际上是放入,而不是空泛地暗示驱动程序可能会喜欢)

      我敢肯定,在现实世界中,我会注意到这个问题,而这个人为的基准测试遇到了一个缓慢的路径,那么我如何才能欺骗驱动程序使缓冲区对象驻留?首先运行计算着色器不会改变任何东西


    您要求驱动程序从进程内存中读取
    dat
    。这会导致大量缓存一致性通信。当GPU读取该内存时,它无法确定它是否是最新的,它可能在CPU缓存中,经过修改,尚未写回RAM。这导致GPU实际上必须从CPU缓存中读取数据,这比绕过CPU读取RAM要昂贵得多。RAM在正常运行期间通常处于空闲状态,因为现代CPU的命中率通常为95%到99%。缓存被连续使用

    为了获得最大的性能,您需要让驱动程序分配内存。程序使用的常规内存(如全局变量和堆)在写回内存中分配。驱动程序分配的内存通常被分配为写合并或不缓存,这消除了一致性通信

    只有在没有缓存一致性开销的情况下才能实现峰值公布带宽数

    要让驱动程序分配数据,请对数据使用
    glBufferData
    nullptr


    不过,如果您设法强迫驱动程序使用系统内存写入组合缓冲区,这并不完全是乐观的。CPU读取这些地址的速度将非常慢。顺序写入由CPU进行优化,但随机写入会导致写入组合缓冲区频繁刷新,从而影响性能。

    CUDA基准测试似乎在测量GPU本地内存带宽,而OpenGL基准测试确实测量PCI-E链路带宽,就像OpenGL驱动程序将对计算结果进行阴影复制一样。我会用计算着色器再试一次。@datenwolf谢谢你的兴趣。我想你的意思是CUDA正在测量GPU全局内存(内核中没有声明)。除非GPU虚拟化系统内存中的缓冲区对象,否则唯一的PCI-E数据传输来自实际基准之前的初始
    glBufferData
    s(并且
    glMapBuffer
    未注释)。对于计算着色器的断言失败,我应该做些什么不同的事情吗?对于
    GPU local
    ,我的意思是“GPU板上本地安装的RAM”,而不是GPU连接到的系统内存(就GPU而言,它是非本地内存,B因为它不通过外围总线就无法访问它)。我不是指CUDA语义。您所引用的数字非常接近GPU本地内存访问与PCI-E链路带宽,这将是我要调查的第一件事。此外,OpenGL实现通常必须为规范实际实现所需的某些内容制作GPU到系统内存的卷影副本。@datenwolf真的!
    assert(glIsBufferResidentNV)
    在测试失败之前。请参阅更新。顶点着色器仍然非常慢。可能
    gl\u VertexID
    在工作组/warp中的着色器之间不是连续的,并且我达到了最差的缓存性能。感谢您的关注!我不相信
    glBufferData
    会保持不变
    image_load_store: 6.66GB/s
    bindless: 6.68GB/s
    bindless compute: 6.65GB/s
    
    //http://www.ks.uiuc.edu/Research/vmd/doxygen/CUDABench_8cu-source.html
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <cuda.h>
    
    #define CUERR { cudaError_t err; \
        if ((err = cudaGetLastError()) != cudaSuccess) { \
        printf("CUDA error: %s, %s line %d\n", cudaGetErrorString(err), __FILE__, __LINE__); \
        return -1; }}
    
    //
    // GPU device global memory bandwidth benchmark
    //
    template <class T>
    __global__ void gpuglobmemcpybw(T *dest, const T *src) {
        const unsigned int idx = threadIdx.x + blockIdx.x * blockDim.x;
        dest[idx] = src[idx];
    }
    
    template <class T>
    __global__ void gpuglobmemsetbw(T *dest, const T val) {
        int idx = threadIdx.x + blockIdx.x * blockDim.x;
        dest[idx] = val;
    }
    
    typedef float4 datatype;
    
    static int cudaglobmembw(int cudadev, double *gpumemsetgbsec, double *gpumemcpygbsec) {
        int i;
        int len = 1 << 22; // one thread per data element
        int loops = 500;
        datatype *src, *dest;
        datatype val=make_float4(1.0f, 1.0f, 1.0f, 1.0f);
    
        // initialize to zero for starters
        float memsettime = 0.0f;
        float memcpytime = 0.0f;
        *gpumemsetgbsec = 0.0;
        *gpumemcpygbsec = 0.0;
    
        // attach to the selected device
        cudaError_t rc;
        rc = cudaSetDevice(cudadev);
        if (rc != cudaSuccess) {
            #if CUDART_VERSION >= 2010
            rc = cudaGetLastError(); // query last error and reset error state
            if (rc != cudaErrorSetOnActiveProcess)
            return -1; // abort and return an error
            #else
            cudaGetLastError(); // just ignore and reset error state, since older CUDA
            // revs don't have a cudaErrorSetOnActiveProcess enum
            #endif
        }
    
        cudaMalloc((void **) &src, sizeof(datatype)*len);
        CUERR
        cudaMalloc((void **) &dest, sizeof(datatype)*len);
        CUERR
    
        dim3 BSz(256, 1, 1);
        dim3 GSz(len / (BSz.x * BSz.y * BSz.z), 1, 1); 
    
        // do a warm-up pass
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(src, val);
        CUERR
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(dest, val);
        CUERR
        gpuglobmemcpybw<datatype><<< GSz, BSz >>>(dest, src);
        CUERR
    
        cudaEvent_t start, end;
        cudaEventCreate(&start);
        cudaEventCreate(&end);
    
        // execute the memset kernel
        cudaEventRecord(start, 0);
        for (i=0; i<loops; i++) {
        gpuglobmemsetbw<datatype><<< GSz, BSz >>>(dest, val);
        }
        CUERR
        cudaEventRecord(end, 0);
        CUERR
        cudaEventSynchronize(start);
        CUERR
        cudaEventSynchronize(end);
        CUERR
        cudaEventElapsedTime(&memsettime, start, end);
        CUERR
    
        // execute the memcpy kernel
        cudaEventRecord(start, 0);
        for (i=0; i<loops; i++) {
        gpuglobmemcpybw<datatype><<< GSz, BSz >>>(dest, src);
        }
        cudaEventRecord(end, 0);
        CUERR
        cudaEventSynchronize(start);
        CUERR
        cudaEventSynchronize(end);
        CUERR
        cudaEventElapsedTime(&memcpytime, start, end);
        CUERR
    
        cudaEventDestroy(start);
        CUERR
        cudaEventDestroy(end);
        CUERR
    
        *gpumemsetgbsec = (len * sizeof(datatype) / (1024.0 * 1024.0)) / (memsettime / loops);
        *gpumemcpygbsec = (2 * len * sizeof(datatype) / (1024.0 * 1024.0)) / (memcpytime / loops);
        cudaFree(dest);
        cudaFree(src);
        CUERR
    
        return 0;
    }
    
    int main()
    {
        double a, b;
        cudaglobmembw(0, &a, &b);
        printf("%f %f\n", (float)a, (float)b);
        return 0;
    }
    
    glBufferData(GL_TEXTURE_BUFFER, bufferSize, dat, GL_STATIC_DRAW);
    glMakeBufferResidentNV(GL_TEXTURE_BUFFER, GL_READ_WRITE);
    glGetBufferParameterui64vNV(GL_TEXTURE_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &address);
    assert(glIsBufferResidentNV(GL_TEXTURE_BUFFER)); //sanity check