¿Cuál es el costo de cambiar de estado?

25

Se supone que los programadores tienen una idea bastante buena del costo de ciertas operaciones: por ejemplo, el costo de una instrucción en la CPU, el costo de una falta de caché L1, L2 o L3, el costo de un LHS.

Cuando se trata de gráficos, me doy cuenta de que tengo poca o ninguna idea de lo que son. Tengo en cuenta que si los ordenamos por costo, los cambios de estado son algo como:

  1. Cambio uniforme de sombreador.
  2. Cambio activo del búfer de vértices.
  3. Cambio de unidad de textura activa.
  4. Cambio de programa de sombreador activo.
  5. Cambio activo del búfer de trama.

Pero esa es una regla general muy tosca, puede que ni siquiera sea correcta, y no tengo idea de cuáles son los órdenes de magnitud. Si tratamos de poner unidades, ns, ciclos de reloj o número de instrucciones, ¿de qué estamos hablando?

Julien Guertault
fuente

Respuestas:

26

La mayoría de los datos que he visto sobre el gasto relativo de varios cambios de estado provienen de la charla de Cass Everitt y John McDonald sobre la reducción de los gastos generales de la API de OpenGL desde enero de 2014. Su charla incluyó esta diapositiva (a las 31:55):

Costos relativos de cambios de estado

La charla no da más información sobre cómo midieron esto (o incluso si están midiendo el costo de la CPU o GPU, ¡o ambos!). Pero al menos encaja con la sabiduría convencional: los cambios en el objetivo de renderizado y el programa de sombreado son los más costosos, las actualizaciones uniformes son las menos, con amortiguadores de vértices y cambios de textura en algún punto intermedio. El resto de su charla también tiene mucha sabiduría interesante sobre la reducción de la sobrecarga de cambio de estado.

Nathan Reed
fuente
2
Estoy seleccionando esta respuesta ya que da órdenes de magnitud, que es la más cercana hasta ahora a lo que pregunté, aunque la fuente mencionada no da mucha explicación.
Julien Guertault
27

El costo real de cualquier cambio de estado en particular varía con tantos factores que una respuesta general es casi imposible.

Primero, cada cambio de estado puede tener un costo tanto del lado de la CPU como del lado de la GPU. El costo de la CPU puede, dependiendo de su controlador y API de gráficos, pagarse completamente en el hilo principal o parcialmente en un hilo de fondo.

En segundo lugar, el costo de la GPU puede depender de la cantidad de trabajo en vuelo. Las GPU modernas están muy canalizadas y les encanta tener mucho trabajo en vuelo a la vez, y la mayor desaceleración que puede obtener es detener la tubería para que todo lo que está actualmente en vuelo deba retirarse antes de que cambie el estado. ¿Qué puede causar un bloqueo de la tubería? ¡Bueno, depende de tu GPU!

Lo que realmente necesita saber para comprender el rendimiento aquí es: ¿qué deben hacer el controlador y la GPU para procesar su cambio de estado? Esto, por supuesto, depende de su GPU y también de los detalles que los ISV a menudo no comparten públicamente. Sin embargo, hay algunos principios generales .

Las GPU generalmente se dividen en un frontend y un backend. El frontend maneja una secuencia de comandos generados por el controlador, mientras que el backend hace todo el trabajo real. Como dije antes, al backend le encanta tener mucho trabajo en vuelo, pero necesita algo de información para almacenar información sobre ese trabajo (quizás completado por la interfaz). Si patea suficientes lotes pequeños y usa todo el silicio para realizar un seguimiento del trabajo, entonces la interfaz tendrá que detenerse incluso si hay muchos caballos de fuerza no utilizados. Entonces, un principio aquí: cuantos más cambios de estado (y pequeños sorteos), más probabilidades hay de que muera de hambre el backend de la GPU .

Mientras se procesa un sorteo, básicamente solo está ejecutando programas de sombreado, que están haciendo accesos a la memoria para obtener sus uniformes, sus datos de búfer de vértices, sus texturas, pero también las estructuras de control que le dicen a las unidades de sombreadores dónde se almacenan sus búferes de vértices y tus texturas son Y la GPU también tiene cachés frente a esos accesos de memoria. Por lo tanto, cada vez que arroje nuevos uniformes o nuevas uniones de textura / búfer a la GPU, es probable que se pierda el caché la primera vez que tenga que leerlos. Otro principio: la mayoría de los cambios de estado provocarán un error de caché de GPU. (Esto es más significativo cuando gestiona buffers constantes usted mismo: si mantiene buffers constantes iguales entre sorteos, entonces es más probable que permanezcan en caché en la GPU).

Una gran parte del costo de los cambios de estado para los recursos del sombreador es el lado de la CPU. Siempre que establezca un nuevo búfer constante, el controlador probablemente copiará el contenido de ese búfer constante en una secuencia de comandos para la GPU. Si establece un solo uniforme, es muy probable que el conductor lo convierta en un gran búfer constante detrás de su espalda, por lo que debe buscar el desplazamiento de ese uniforme en el búfer constante, copiar el valor y luego marcar el búfer constante tan sucio que puede copiarse en la secuencia de comandos antes de la próxima llamada de sorteo. Si vincula una nueva textura o un búfer de vértices, el controlador probablemente esté copiando una estructura de control para ese recurso. Además, si está utilizando una GPU discreta en un sistema operativo multitarea, el controlador debe rastrear cada recurso que utiliza y cuando comienza a usarlo para que el núcleo ' El administrador de memoria de la GPU puede garantizar que la memoria de ese recurso resida en la VRAM de la GPU cuando ocurra el sorteo. Principio:Los cambios de estado hacen que el controlador baraje la memoria para generar una secuencia de comandos mínima para la GPU.

Cuando cambia el sombreador actual, probablemente esté causando una pérdida de caché de GPU (¡también tienen un caché de instrucciones!). En principio, el trabajo de la CPU debe limitarse a poner un nuevo comando en la secuencia de comandos que diga "use el sombreador". En realidad, sin embargo, hay un montón de compilación de sombreadores para tratar. Los controladores de GPU a menudo compilan sombreadores de manera perezosa, incluso si ha creado el sombreador con anticipación. Sin embargo, más relevante para este tema, algunos estados no son compatibles de forma nativa con el hardware de la GPU y, en cambio, se compilan en el programa de sombreado. Un ejemplo popular son los formatos de vértices: estos pueden compilarse en el sombreador de vértices en lugar de estar separados en el chip. Entonces, si usa formatos de vértices que no ha usado antes con un sombreador de vértices en particular, ahora puede estar pagando un montón de costos de CPU para parchear el sombreador y copiar el programa del sombreador a la GPU. Además, el controlador y el compilador del sombreador pueden conspirar para hacer todo tipo de cosas para optimizar la ejecución del programa del sombreador. Esto podría significar optimizar el diseño de la memoria de sus uniformes y estructuras de control de recursos para que estén bien empaquetados en la memoria adyacente o en los registros del sombreador. Entonces, cuando cambia los sombreadores, puede hacer que el controlador mire todo lo que ya ha vinculado a la tubería y lo reempaque en un formato completamente diferente para el nuevo sombreador, y luego lo copie en la secuencia de comandos. Principio: Esto podría significar optimizar el diseño de la memoria de sus uniformes y estructuras de control de recursos para que estén bien empaquetados en la memoria adyacente o en los registros del sombreador. Entonces, cuando cambia los sombreadores, puede hacer que el controlador mire todo lo que ya ha vinculado a la tubería y lo reempaque en un formato completamente diferente para el nuevo sombreador, y luego lo copie en la secuencia de comandos. Principio: Esto podría significar optimizar el diseño de la memoria de sus uniformes y estructuras de control de recursos para que estén bien empaquetados en la memoria adyacente o en los registros del sombreador. Entonces, cuando cambia los sombreadores, puede hacer que el controlador mire todo lo que ya ha vinculado a la tubería y lo reempaque en un formato completamente diferente para el nuevo sombreador, y luego lo copie en la secuencia de comandos. Principio:El cambio de sombreadores puede causar una gran cantidad de memoria de la CPU.

Los cambios en el búfer de trama son probablemente los más dependientes de la implementación, pero generalmente son bastante caros en la GPU. Es posible que su GPU no pueda manejar múltiples llamadas de extracción a diferentes objetivos de representación al mismo tiempo, por lo que es posible que deba detener la tubería entre esas dos llamadas de extracción. Es posible que necesite vaciar cachés para que el destino de renderizado pueda leerse más tarde. Es posible que deba resolver el trabajo que ha pospuesto durante el sorteo. (Es muy común acumular una estructura de datos separada junto con zonas de amortiguación de profundidad, objetivos de representación de MSAA y más. Esto puede necesitar finalizarse cuando se aleja de ese objetivo de representación. Si está en una GPU basada en mosaicos , al igual que muchas GPU móviles, es posible que sea necesario enjuagar una gran cantidad de trabajo de sombreado real cuando se cambia de un búfer de cuadros.) Principio:Cambiar los objetivos de renderizado es costoso en la GPU.

Estoy seguro de que todo es muy confuso, y desafortunadamente es difícil ser demasiado específico porque los detalles a menudo no son públicos, pero espero que sea una descripción medio decente de algunas de las cosas que realmente están sucediendo cuando llamas a algún estado función cambiante en su API gráfica favorita.

John Calsbeek
fuente