uso usar tutorial paso osciloscopio manejo guia conectar como c++ opengl glsl nvidia

c++ - usar - ¿Cómo se mide el ancho de banda máximo de memoria en OpenGL?



uso del osciloscopio digital (1)

Le está pidiendo al controlador que lea de su memoria de proceso, dat . Esto provoca un tráfico de coherencia de caché extenso. Cuando la GPU lee esa memoria, no puede estar seguro de que esté actualizada, podría estar en la memoria caché de la CPU, modificada, y aún no escrita en la RAM. Esto hace que la GPU tenga que leer realmente desde la memoria caché de la CPU, que es mucho más costosa que pasar por alto la CPU y leer la memoria RAM. La RAM suele estar inactiva durante el funcionamiento normal, porque la tasa de aciertos de una CPU moderna suele ser del 95% al ​​99%. El caché se utiliza continuamente.

Para lograr el máximo rendimiento, debe permitir que el controlador asigne la memoria. La memoria normal que utiliza su programa, como las variables globales y el montón, se asignan en la memoria de reescritura . La memoria asignada por el controlador generalmente se asignará como combinación de escritura o incachable , lo que elimina el tráfico de coherencia.

Los números máximos de ancho de banda anunciados se lograrán solo sin la sobrecarga de coherencia de caché.

Para permitir que el controlador lo asigne, use glBufferData con un nullptr para los datos.

Sin embargo, no todo es color de rosa, si logras obligar al conductor a usar un búfer de combinación de escritura de memoria del sistema. Las lecturas de la CPU a tales direcciones serán muy lentas. Las escrituras secuenciales están optimizadas por la CPU, pero las escrituras aleatorias causarán que la escritura del búfer de combinación se vacíe con frecuencia, lo que perjudicará el rendimiento.

Solo para tener una idea de qué tipo de velocidades debería esperar, he estado tratando de comparar la transferencia entre la memoria global y los sombreadores, en lugar de confiar en las hojas de especificaciones de la GPU. Sin embargo no puedo acercarme al máximo teórico. De hecho estoy fuera por un factor de 50 !

Estoy usando un GTX Titan X, que se dice que tiene 336.5GB / s . Controlador Linux x64 352.21.

Encontré un punto de referencia CUDA here que me da ~ 240–250GB / s (esto es más de lo que esperaba).

Estoy tratando de hacer coincidir exactamente lo que hacen con los shaders. He intentado shaders de vértices, shaders de cómputo, acceso a objetos de búfer a través de image_load_store y NV_shader_buffer_store , con float s, vec4 s, bucles dentro del shader (con direccionamiento unido dentro del grupo de trabajo) y varios métodos de sincronización. Estoy atascado en ~ 7GB / s ( ver la actualización a continuación ).

¿Por qué GL es mucho más lento? ¿Estoy haciendo algo mal y, de ser así, cómo debería hacerse?

Aquí está mi MWE con tres métodos (1. sombreado de vértice con image_load_store, 2. sombreador de vértice con gráficos sin encuadernación, 3. sombreador de cálculo con gráficos sin encuadernación):

//#include <windows.h> #include <assert.h> #include <stdio.h> #include <memory.h> #include <GL/glew.h> #include <GL/glut.h> const char* imageSource = "#version 440/n" "uniform layout(r32f) imageBuffer data;/n" "uniform float val;/n" "void main() {/n" " imageStore(data, gl_VertexID, vec4(val, 0.0, 0.0, 0.0));/n" " gl_Position = vec4(0.0);/n" "}/n"; const char* bindlessSource = "#version 440/n" "#extension GL_NV_gpu_shader5 : enable/n" "#extension GL_NV_shader_buffer_load : enable/n" "uniform float* data;/n" "uniform float val;/n" "void main() {/n" " data[gl_VertexID] = val;/n" " gl_Position = vec4(0.0);/n" "}/n"; const char* bindlessComputeSource = "#version 440/n" "#extension GL_NV_gpu_shader5 : enable/n" "#extension GL_NV_shader_buffer_load : enable/n" "layout(local_size_x = 256) in;/n" "uniform float* data;/n" "uniform float val;/n" "void main() {/n" " data[gl_GlobalInvocationID.x] = val;/n" "}/n"; GLuint compile(GLenum type, const char* shaderSrc) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, (const GLchar**)&shaderSrc, NULL); glCompileShader(shader); int success = 0; int loglen = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &loglen); GLchar* log = new GLchar[loglen]; glGetShaderInfoLog(shader, loglen, &loglen, log); if (!success) { printf("%s/n", log); exit(0); } GLuint program = glCreateProgram(); glAttachShader(program, shader); glLinkProgram(program); return program; } GLuint timerQueries[2]; void start() { glGenQueries(2, timerQueries); glQueryCounter(timerQueries[0], GL_TIMESTAMP); } float stop() { glMemoryBarrier(GL_ALL_BARRIER_BITS); GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glQueryCounter(timerQueries[1], GL_TIMESTAMP); GLint available = 0; while (!available) //sometimes gets stuck here for whatever reason glGetQueryObjectiv(timerQueries[1], GL_QUERY_RESULT_AVAILABLE, &available); GLuint64 a, b; glGetQueryObjectui64v(timerQueries[0], GL_QUERY_RESULT, &a); glGetQueryObjectui64v(timerQueries[1], GL_QUERY_RESULT, &b); glDeleteQueries(2, timerQueries); return b - a; } int main(int argc, char** argv) { float* check; glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutCreateWindow("test"); glewInit(); int bufferSize = 64 * 1024 * 1024; //64MB int loops = 500; glEnable(GL_RASTERIZER_DISCARD); float* dat = new float[bufferSize/sizeof(float)]; memset(dat, 0, bufferSize); //create a buffer with data GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_TEXTURE_BUFFER, buffer); glBufferData(GL_TEXTURE_BUFFER, bufferSize, NULL, GL_STATIC_DRAW); //get a bindless address GLuint64 address; glMakeBufferResidentNV(GL_TEXTURE_BUFFER, GL_READ_WRITE); glGetBufferParameterui64vNV(GL_TEXTURE_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &address); //make a texture alias for it GLuint bufferTexture; glGenTextures(1, &bufferTexture); glBindTexture(GL_TEXTURE_BUFFER, bufferTexture); glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, buffer); glBindImageTextureEXT(0, bufferTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32F); //compile the shaders GLuint imageShader = compile(GL_VERTEX_SHADER, imageSource); GLuint bindlessShader = compile(GL_VERTEX_SHADER, bindlessSource); GLuint bindlessComputeShader = compile(GL_COMPUTE_SHADER, bindlessComputeSource); //warm-up and check values glBufferData(GL_TEXTURE_BUFFER, bufferSize, dat, GL_STATIC_DRAW); glUseProgram(imageShader); glUniform1i(glGetUniformLocation(imageShader, "data"), 0); glUniform1f(glGetUniformLocation(imageShader, "val"), 1.0f); glDrawArrays(GL_POINTS, 0, bufferSize/sizeof(float)); glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); //check = (float*)glMapBuffer(GL_TEXTURE_BUFFER, GL_READ_ONLY); //for (int i = 0; i < bufferSize/sizeof(float); ++i) // assert(check[i] == 1.0f); //glUnmapBuffer(GL_TEXTURE_BUFFER); glBufferData(GL_TEXTURE_BUFFER, bufferSize, dat, GL_STATIC_DRAW); glUseProgram(bindlessShader); glProgramUniformui64NV(bindlessShader, glGetUniformLocation(bindlessShader, "data"), address); glUniform1f(glGetUniformLocation(bindlessShader, "val"), 1.0f); glDrawArrays(GL_POINTS, 0, bufferSize/sizeof(float)); //glMemoryBarrier(GL_ALL_BARRIER_BITS); //this causes glDispatchCompute to segfault later, so don''t uncomment //check = (float*)glMapBuffer(GL_TEXTURE_BUFFER, GL_READ_ONLY); //for (int i = 0; i < bufferSize/sizeof(float); ++i) // assert(check[i] == 1.0f); //glUnmapBuffer(GL_TEXTURE_BUFFER); glBufferData(GL_TEXTURE_BUFFER, bufferSize, dat, GL_STATIC_DRAW); glUseProgram(bindlessComputeShader); glProgramUniformui64NV(bindlessComputeShader, glGetUniformLocation(bindlessComputeShader, "data"), address); glUniform1f(glGetUniformLocation(bindlessComputeShader, "val"), 1.0f); glDispatchCompute(bufferSize/(sizeof(float) * 256), 1, 1); glMemoryBarrier(GL_ALL_BARRIER_BITS); //check = (float*)glMapBuffer(GL_TEXTURE_BUFFER, GL_READ_ONLY); //for (int i = 0; i < bufferSize/sizeof(float); ++i) // assert(check[i] == 1.0f); //glDispatchCompute doesn''t actually write anything with bindless graphics //glUnmapBuffer(GL_TEXTURE_BUFFER); glFinish(); //time image_load_store glUseProgram(imageShader); glUniform1i(glGetUniformLocation(imageShader, "data"), 0); glUniform1f(glGetUniformLocation(imageShader, "val"), 1.0f); start(); for (int i = 0; i < loops; ++i) glDrawArrays(GL_POINTS, 0, bufferSize/sizeof(float)); GLuint64 imageTime = stop(); printf("image_load_store: %.2fGB/s/n", (float)((bufferSize * (double)loops) / imageTime)); //time bindless glUseProgram(bindlessShader); glProgramUniformui64NV(bindlessShader, glGetUniformLocation(bindlessShader, "data"), address); glUniform1f(glGetUniformLocation(bindlessShader, "val"), 1.0f); start(); for (int i = 0; i < loops; ++i) glDrawArrays(GL_POINTS, 0, bufferSize/sizeof(float)); GLuint64 bindlessTime = stop(); printf("bindless: %.2fGB/s/n", (float)((bufferSize * (double)loops) / bindlessTime)); //time bindless in a compute shader glUseProgram(bindlessComputeShader); glProgramUniformui64NV(bindlessComputeShader, glGetUniformLocation(bindlessComputeShader, "data"), address); glUniform1f(glGetUniformLocation(bindlessComputeShader, "val"), 1.0f); start(); for (int i = 0; i < loops; ++i) glDispatchCompute(bufferSize/(sizeof(float) * 256), 1, 1); GLuint64 bindlessComputeTime = stop(); printf("bindless compute: %.2fGB/s/n", (float)((bufferSize * (double)loops) / bindlessComputeTime)); assert(glGetError() == GL_NO_ERROR); return 0; }

Mi salida:

image_load_store: 6.66GB/s bindless: 6.68GB/s bindless compute: 6.65GB/s

Algunas notas:

  1. Los sombreadores informáticos con gráficos sin encuadernación no parecen escribir nada (la afirmación comentada falla), o al menos los datos no se recuperan con glMapBuffer aunque la velocidad coincida con los otros métodos. El uso de image_load_store en el sombreador de cómputo funciona y da la misma velocidad que los sombreadores de vértice (aunque pensé que sería una permutación demasiada para publicar).
  2. Llamar a glMemoryBarrier(GL_ALL_BARRIER_BITS) antes de que glDispatchCompute cause un bloqueo en el controlador.
  3. Comentando los tres glBufferData(GL_TEXTURE_BUFFER, bufferSize, dat, GL_STATIC_DRAW); , que se usa para verificar la salida, aumenta la velocidad de las dos primeras pruebas a 17GB / sy el shader de cómputo se dispara a 292GB / s, lo cual está mucho más cerca de lo que me gustaría, pero esto no se puede confiar debido al punto 1.
  4. A veces, while (!available) cuelga durante años (ctrl-c cuando me canso de esperar, muestra que todavía está en el circuito).

Para referencia, aquí está el código 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; }

Actualizar:

Parece que el búfer se hace no residente en mis llamadas glBufferData que estaban allí para comprobar que la salida se estaba escribiendo. Según la extensión :

Un búfer también se convierte en no residente de forma implícita como resultado de ser re-especificado a través de BufferData o de ser eliminado.
...
BufferData se especifica para "eliminar el almacén de datos existente", por lo que la dirección de la GPU de esos datos debería ser inválida. Por lo tanto, el búfer se hace no residente en el contexto actual.

En una conjetura, OpenGL luego transmite los datos del objeto del búfer en cada fotograma y no los almacena en la memoria de video. Esto explica por qué el shader de cálculo falló en la afirmación, sin embargo, existe una ligera anomalía en el hecho de que los gráficos sin enlace en el shader de vértice aún funcionaban cuando no eran residentes, pero por ahora lo ignoraré. No tengo idea de por qué un objeto de búfer de 64 MB no sería el residente (aunque quizás después del primer uso) cuando hay 12 GB disponibles.

Entonces, después de cada llamada a glBufferData lo hago residente de nuevo y obtengo la dirección en caso de que cambie:

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

Ahora estoy obteniendo 270–290GB / s con el sombreador de cómputo usando image_load_store o gráficos sin encuadernación. Ahora mi pregunta incluye :

  • Dado que el búfer parece ser residente para cada prueba y el sombreador de cálculo es bueno y rápido, ¿por qué las versiones de sombreado de vértices son tan lentas?
  • Sin la extensión de gráficos sin encuadernación, ¿cómo deberían los usuarios regulares de OpenGL poner datos en la memoria de video (en realidad poner y no sugerir de forma ociosa que al controlador le gustaría)?

    Estoy bastante seguro de que me habría dado cuenta de este problema en situaciones del mundo real, y es este punto de referencia ideado el que se mueve lentamente, así que, ¿cómo podría engañar al conductor para que haga que un objeto de búfer resida? Ejecutar un sombreador de cálculo primero no cambia nada.