performance poner - ¿La divergencia de las ramas es realmente tan mala?




to partir (2)

He visto muchas preguntas dispersas en Internet sobre la divergencia de sucursales y cómo evitarla . Sin embargo, incluso después de leer docenas de artículos sobre cómo funciona CUDA, parece que no veo cómo evitar la divergencia de ramas ayuda en la mayoría de los casos . Antes de que nadie salte sobre mí con las garras extendidas, permítame describir lo que considero que es "la mayoría de los casos".

Me parece que la mayoría de los casos de divergencia de ramas involucran una cantidad de bloques de código verdaderamente distintos. Por ejemplo, tenemos el siguiente escenario:

if (A):
  foo(A)
else:
  bar(B)

Si tenemos dos hilos que encuentran esta divergencia, el hilo 1 se ejecutará primero, tomando el camino A. A continuación, el hilo 2 tomará el camino B. Para eliminar la divergencia, podríamos cambiar el bloque anterior para que se lea así:

foo(A)
bar(B)

Suponiendo que es seguro llamar a foo(A) en el subproceso 2 y la bar(B) en el subproceso 1, se puede esperar que mejore el rendimiento. Sin embargo, así es como lo veo:

En el primer caso, los hilos 1 y 2 se ejecutan en serie. Llamar a estos dos ciclos de reloj.

En el segundo caso, los hilos 1 y 2 ejecutan foo(A) en paralelo, luego ejecutan la bar(B) en paralelo. Esto todavía me parece como dos ciclos de reloj, la diferencia es que en el primer caso, si foo(A) implica una lectura desde la memoria, imagino que el subproceso 2 puede comenzar a ejecutarse durante esa latencia, lo que da como resultado la latencia oculta. Si este es el caso, el código divergente de la sucursal es más rápido.


Answers

Estás asumiendo (al menos es el ejemplo que das y la única referencia que haces) que la única forma de evitar la divergencia de ramas es permitir que todos los hilos ejecuten todo el código.

En ese caso, estoy de acuerdo en que no hay mucha diferencia.

Pero evitar la divergencia de ramas probablemente tiene más que ver con la reestructuración del algoritmo en un nivel más alto que simplemente la adición o eliminación de algunas sentencias if y hacer que el código sea "seguro" para ejecutar en todos los hilos.

Ofreceré un ejemplo. Supongamos que sé que los hilos impares necesitarán manejar el componente azul de un píxel e incluso los hilos necesitarán manejar el componente verde:

#define N 2 // number of pixel components
#define BLUE 0
#define GREEN 1
// pixel order: px0BL px0GR px1BL px1GR ...


if (threadIdx.x & 1)  foo(pixel(N*threadIdx.x+BLUE));
else                  bar(pixel(N*threadIdx.x+GREEN));

Esto significa que cada hilo alternativo toma una ruta determinada, ya sea foo o bar . Entonces ahora mi Warp tarda el doble de tiempo en ejecutarse.

Sin embargo, si reorganizo mis datos de píxeles para que los componentes de color sean contiguos, tal vez en fragmentos de 32 píxeles: BL0 BL1 BL2 ... GR0 GR1 GR2 ...

Puedo escribir un código similar:

if (threadIdx.x & 32)  foo(pixel(threadIdx.x));
else                   bar(pixel(threadIdx.x));

Todavía parece que tengo la posibilidad de divergencia. Pero dado que la divergencia ocurre en los límites de warp, un warp de give ejecuta ya sea la ruta if o la ruta else , por lo que no ocurre ninguna divergencia real.

Este es un ejemplo trivial, y probablemente estúpido, pero ilustra que puede haber formas de evitar la divergencia de la distorsión que no implique ejecutar todo el código de todas las rutas divergentes.


En primer lugar, como señalan @AndrewFinnell y @KenLiu, en SVN los nombres de los directorios en sí mismos no significan nada, "troncales, ramas y etiquetas" son simplemente una convención común que es utilizada por la mayoría de los repositorios. No todos los proyectos usan todos los directorios (es bastante común que no se usen "etiquetas" en absoluto), y de hecho, nada impide que los llames como te gustaría, aunque romper la convención es a menudo confuso.

Describiré probablemente el escenario de uso más común de ramas y etiquetas, y daré un ejemplo de cómo se usan.

  • Tronco : principal área de desarrollo. Aquí es donde vive su próxima versión importante del código, y generalmente tiene todas las características más nuevas.

  • Ramas : Cada vez que liberas una versión principal, se crea una rama. Esto le permite hacer correcciones de errores y hacer una nueva versión sin tener que liberar las funciones más nuevas, posiblemente sin terminar o no probadas.

  • Etiquetas : cada vez que lanzas una versión (versión final, versión candidata (RC) y betas) creas una etiqueta para ella. Esto le da una copia puntual del código tal como estaba en ese estado, lo que le permite volver atrás y reproducir cualquier error si es necesario en una versión anterior, o volver a lanzar una versión anterior exactamente como estaba. Las ramas y etiquetas en SVN son ligeras: en el servidor, no hace una copia completa de los archivos, solo un marcador que dice "estos archivos se copiaron en esta revisión" que solo ocupa unos pocos bytes. Con esto en mente, nunca debe preocuparse por crear una etiqueta para cualquier código publicado. Como dije anteriormente, las etiquetas a menudo se omiten y, en cambio, un registro de cambios u otro documento aclara el número de revisión cuando se realiza una publicación.

Por ejemplo, digamos que empiezas un nuevo proyecto. Comienzas a trabajar en "troncal", en lo que finalmente se lanzará como la versión 1.0.

  • tronco / - versión de desarrollo, que pronto será 1.0
  • ramas / - vacio

Una vez que finaliza la versión 1.0.0, ramifica el tronco en una nueva rama "1.0" y crea una etiqueta "1.0.0". Ahora el trabajo en lo que eventualmente será 1.1 continúa en baúl.

  • tronco / - versión de desarrollo, que pronto será 1.1
  • sucursales / 1.0 - 1.0.0 versión de lanzamiento
  • tags / 1.0.0 - 1.0.0 versión de lanzamiento

Te encuentras con algunos errores en el código, los arreglas en el tronco y luego fusionas los arreglos en la rama 1.0. También puede hacer lo contrario, y corregir los errores en la rama 1.0 y luego combinarlos de nuevo en el tronco, pero normalmente los proyectos se adhieren a la fusión de una sola vía para disminuir la posibilidad de perder algo. A veces, un error solo se puede corregir en 1.0 porque está obsoleto en 1.1. Realmente no importa: solo quiere asegurarse de no lanzar la versión 1.1 con los mismos errores que se han corregido en la versión 1.0.

  • tronco / - versión de desarrollo, que pronto será 1.1
  • sucursales / 1.0 - próxima versión 1.0.1
  • tags / 1.0.0 - 1.0.0 versión de lanzamiento

Una vez que encuentre suficientes errores (o tal vez un error crítico), decide hacer una versión 1.0.1. Así que crea una etiqueta "1.0.1" desde la rama 1.0 y libera el código. En este punto, el troncal contendrá lo que será 1.1, y la rama "1.0" contiene el código 1.0.1. La próxima vez que lance una actualización a 1.0, sería 1.0.2.

  • tronco / - versión de desarrollo, que pronto será 1.1
  • sucursales / 1.0 - próxima versión 1.0.2
  • tags / 1.0.0 - 1.0.0 versión de lanzamiento
  • tags / 1.0.1 - 1.0.1 versión de lanzamiento

Eventualmente, estás casi listo para la versión 1.1, pero primero quieres hacer una versión beta. En este caso, es probable que hagas una rama "1.1" y una etiqueta "1.1beta1". Ahora, el trabajo en lo que será 1.2 (o 2.0 tal vez) continúa en el tronco, pero el trabajo en 1.1 continúa en la rama "1.1".

  • tronco / - versión de desarrollo, que pronto será 1.2
  • sucursales / 1.0 - próxima versión 1.0.2
  • sucursales / 1.1 - próximo lanzamiento 1.1.0
  • tags / 1.0.0 - 1.0.0 versión de lanzamiento
  • tags / 1.0.1 - 1.0.1 versión de lanzamiento
  • tags / 1.1beta1 - 1.1 beta 1 versión de lanzamiento

Una vez que lanzas la versión 1.1 final, haces una etiqueta "1.1" de la rama "1.1".

También puede continuar manteniendo 1.0 si lo desea, portando correcciones de errores entre las tres ramas (1.0, 1.1 y troncal). Lo importante es que por cada versión principal del software que está manteniendo, tiene una rama que contiene la última versión del código para esa versión.

Otro uso de las ramas es para características. Aquí es donde se bifurca el tronco (o una de sus ramas de lanzamiento) y se trabaja en una nueva función de forma aislada. Una vez que se completa la función, vuelve a combinarla y elimina la rama.

  • tronco / - versión de desarrollo, que pronto será 1.2
  • sucursales / 1.1 - próximo lanzamiento 1.1.0
  • sucursales / ui-rewrite - rama característica experimental

La idea de esto es cuando estás trabajando en algo disruptivo (que podría retrasar o interferir con otras personas para que no realicen su trabajo), algo experimental (que puede que ni siquiera lo logre), o posiblemente algo que demore mucho tiempo. (y tiene miedo si se sostiene una versión 1.2 cuando esté listo para derivar 1.2 desde el tronco), puede hacerlo de forma aislada en la rama. Por lo general, lo mantiene actualizado con el tronco combinando cambios en él todo el tiempo, lo que hace que sea más fácil volver a integrarlo (fusionarse con el tronco) cuando haya terminado.

También tenga en cuenta que el esquema de versiones que usé aquí es solo uno de muchos. Algunos equipos hacen correcciones de errores / versiones de mantenimiento como 1.1, 1.2, etc., y cambios importantes como 1.x, 2.x, etc. El uso aquí es el mismo, pero puede llamar a la rama "1" o "1 .x "en lugar de" 1.0 "o" 1.0.x ". (Aparte, el control de versiones semántico es una buena guía sobre cómo hacer números de versión).





performance cuda branch