Costo de mantenimiento de la base del código de programación SIMD

14

Pregunta:

El consenso de la industria del software es que el código limpio y simple es fundamental para la viabilidad a largo plazo de la base del código y la organización que lo posee. Estas propiedades conducen a menores costos de mantenimiento y a una mayor probabilidad de que se continúe la base del código.

Sin embargo, el código SIMD es diferente al código de aplicación general, y me gustaría saber si existe un consenso similar con respecto al código limpio y simple que se aplica específicamente al código SIMD.


Antecedentes de mi pregunta.

Escribo mucho código SIMD (instrucción única, datos múltiples) para varias tareas de procesamiento y análisis de imágenes. Recientemente también tuve que portar una pequeña cantidad de estas funciones de una arquitectura (SSE2) a otra (ARM NEON).

El código está escrito para software reducido, por lo tanto, no puede depender de lenguajes propietarios sin derechos de redistribución sin restricciones, como MATLAB.

Un ejemplo de estructura de código típica:

  • Utilizando el tipo de matriz de OpenCV ( Mat) para toda la memoria, el búfer y la gestión de la vida útil.
  • Después de verificar el tamaño (dimensiones) de los argumentos de entrada, se toman los punteros a la dirección inicial de cada fila de píxeles.
  • El recuento de píxeles y las direcciones de inicio de cada fila de píxeles de cada matriz de entrada se pasan a algunas funciones de C ++ de bajo nivel.
  • Estas funciones de C ++ de bajo nivel utilizan intrínsecos SIMD (para Intel Architecture y ARM NEON ), cargando y guardando en direcciones de puntero sin formato.
  • Características de estas funciones de C ++ de bajo nivel:
    • Exclusivamente unidimensional (consecutiva en memoria)
    • No trata con asignaciones de memoria.
      (Cada asignación, incluidos los temporales, son manejados por el código externo utilizando las instalaciones de OpenCV).
    • El rango de longitud de los nombres de los símbolos (intrínsecos, nombres de variables, etc.) son aproximadamente de 10 a 20 caracteres, lo cual es bastante excesivo.
      (Se lee como techno-balbuceo).
    • Se desaconseja la reutilización de las variables SIMD porque los compiladores son bastante defectuosos al analizar correctamente el código que no está escrito en el estilo de codificación de "asignación única".
      (He presentado varios informes de errores del compilador).

¿Qué aspectos de la programación SIMD causarían que la discusión difiera del caso general? O, ¿por qué es diferente SIMD?

En términos de costo de desarrollo inicial

  • Es bien sabido que el costo de desarrollo inicial del código SIMD C ++ con buen rendimiento es de aproximadamente 10x - 100x (con un amplio margen) en comparación con el código C ++ escrito casualmente .
  • Como se señaló en las respuestas a Elegir entre rendimiento vs código legible / más limpio? , la mayoría del código (incluido el código escrito casualmente y el código SIMD) inicialmente no es ni limpio ni rápido .
  • Se desaconsejan las mejoras evolutivas en el rendimiento del código (tanto en el código escalar como en el SIMD) (porque se considera una especie de retrabajo del software ), y no se realiza un seguimiento del costo y el beneficio.

En términos de propensión
(por ejemplo, el principio de Pareto, también conocido como la regla 80-20 )

  • Incluso si el procesamiento de imágenes solo comprende el 20% de un sistema de software (tanto en tamaño de código como en funcionalidad), el procesamiento de imágenes es relativamente lento (cuando se ve como un porcentaje del tiempo de CPU invertido), lo que lleva más del 80% del tiempo.
    • Esto se debe al efecto del tamaño de los datos: un tamaño de imagen típico se mide en megabytes, mientras que el tamaño típico de los datos que no son de imagen se mide en kilobytes.
  • Dentro del código de procesamiento de imágenes, un programador SIMD está capacitado para reconocer automáticamente el código del 20% que comprende los puntos calientes mediante la identificación de la estructura de bucle en el código C ++. Por lo tanto, desde la perspectiva de un programador SIMD, el 100% del "código que importa" es un cuello de botella en el rendimiento.
  • A menudo, en un sistema de procesamiento de imágenes, existen varios puntos de acceso y ocupan proporciones de tiempo comparables. Por ejemplo, puede haber 5 puntos calientes cada uno ocupando (20%, 18%, 16%, 14%, 12%) del tiempo total. Para lograr una ganancia de alto rendimiento, todos los puntos de acceso deben reescribirse en SIMD.
    • Esto se resume como la regla de reventar globos: un globo no se puede reventar dos veces.
    • Supongamos que hay algunos globos, digamos 5 de ellos. La única forma de diezmarlos es hacerlos estallar uno por uno.
    • Una vez que aparece el primer globo, los 4 globos restantes ahora comprenden un mayor porcentaje del tiempo total de ejecución.
    • Para obtener más ganancias, uno debe reventar otro globo.
      (Esto es en desafío a la regla de optimización 80-20: un buen resultado económico puede lograrse después de que se ha recogido el 20% de las frutas más bajo colgantes.)

En términos de legibilidad y mantenimiento.

  • El código SIMD es patentemente difícil de leer.

    • Esto es cierto incluso si se siguen todas las mejores prácticas de ingeniería de software, por ejemplo, nomenclatura, encapsulación, corrección constante (y hacer obvios los efectos secundarios), descomposición de funciones, etc.
    • Esto es cierto incluso para programadores SIMD experimentados.
  • El código SIMD óptimo está muy contorsionado (ver observación) en comparación con su código prototipo C ++ equivalente.

    • Hay muchas formas de contorsionar el código SIMD, pero solo 1 de cada 10 de estos intentos alcanzará resultados aceptablemente rápidos.
    • (Es decir, en las melodías de ganancias de rendimiento 4x-10x para justificar el alto costo de desarrollo. Incluso se han observado ganancias más altas en la práctica).

(Observación)
Esta es la tesis principal del proyecto MIT Halide , citando el título del artículo al pie de la letra:

"algoritmos de desacoplamiento de programaciones para una fácil optimización de las canalizaciones de procesamiento de imágenes"

En términos de aplicabilidad futura

  • El código SIMD está estrictamente vinculado a una sola arquitectura. Cada nueva arquitectura (o cada ampliación de los registros SIMD) requiere una reescritura.
  • A diferencia de la mayoría del desarrollo de software, cada parte del código SIMD generalmente se escribe con un único propósito que nunca cambia.
    (Con la excepción de portar a otras arquitecturas).
  • Algunas arquitecturas mantienen una compatibilidad hacia atrás perfecta (Intel); algunos se quedan cortos en una cantidad trivial (ARM AArch64, reemplazando vtblcon vtblq) pero que es suficiente para causar que algún código no se compile.

En cuanto a habilidades y entrenamiento

  • No está claro qué requisitos previos de conocimiento se requieren para capacitar adecuadamente a un nuevo programador para escribir y mantener el código SIMD.
  • Los graduados universitarios que han aprendido la programación SIMD en la escuela parecen despreciarla y descartarla como una carrera profesional poco práctica.
  • La lectura de desmontaje y el perfil de rendimiento de bajo nivel se citan como dos habilidades fundamentales para escribir código SIMD de alto rendimiento. Sin embargo, no está claro cómo capacitar sistemáticamente a los programadores en estas dos habilidades.
  • La arquitectura moderna de la CPU (que difiere significativamente de lo que se enseña en los libros de texto) hace que la capacitación sea aún más difícil.

En términos de corrección y costos relacionados con defectos

  • Una sola función de procesamiento SIMD es en realidad lo suficientemente cohesiva como para establecer la corrección mediante:
    • Aplicar métodos formales (con lápiz y papel) , y
    • Verificación de rangos enteros de salida (con código prototipo y realizado fuera del tiempo de ejecución) .
  • Sin embargo, el proceso de verificación es muy costoso (pasa el 100% del tiempo en la revisión del código y el 100% del tiempo en la verificación del modelo de prototipo), lo que triplica el costo de desarrollo ya costoso del código SIMD.
  • Si un error de alguna manera logra pasar este proceso de verificación, es casi imposible "reparar" (arreglar) excepto reemplazar (reescribir) la función defectuosa sospechosa.
  • El código SIMD adolece de defectos contundentes en el compilador de C ++ (generador de código de optimización).
    • El código SIMD generado usando plantillas de expresión C ++ también sufre en gran medida por defectos del compilador.

En términos de innovaciones disruptivas

  • Se han propuesto muchas soluciones de la academia, pero pocas están viendo un uso comercial generalizado.

    • Haluro de MIT
    • Stanford Darkroom
    • NT2 (Caja de herramientas de plantilla numérica) y el Boost.SIMD relacionado
  • Las bibliotecas con uso comercial generalizado no parecen estar muy habilitadas para SIMD.

    • Las bibliotecas de código abierto parecen tibias para SIMD.
      • Recientemente tengo esta observación de primera mano de esto después de perfilar una gran cantidad de funciones de API OpenCV, a partir de la versión 2.4.9.
      • Muchas otras bibliotecas de procesamiento de imágenes que he perfilado tampoco hacen un uso intensivo de SIMD, o pierden los verdaderos puntos de acceso.
    • Las bibliotecas comerciales parecen evitar SIMD por completo.
      • En algunos casos, incluso he visto bibliotecas de procesamiento de imágenes que revierten el código optimizado SIMD en una versión anterior a código no SIMD en una versión posterior, lo que resulta en severas regresiones de rendimiento.
        (La respuesta del proveedor es que era necesario evitar errores del compilador).

La pregunta de este programador: ¿el código de baja latencia a veces tiene que ser "feo"? está relacionado, y anteriormente escribí una respuesta a esa pregunta para explicar mis puntos de vista hace unos años.

Sin embargo, esa respuesta es más o menos "apaciguamiento" al punto de vista de "optimización prematura", es decir, al punto de vista de que:

  • Todas las optimizaciones son prematuras por definición (o, a corto plazo por naturaleza ), y
  • La única optimización que tiene un beneficio a largo plazo es hacia la simplicidad.

Pero tales puntos de vista se disputan en este artículo de ACM .


Todo eso me lleva a preguntar: el
código SIMD es diferente al código de aplicación general, y me gustaría saber si existe un consenso similar en la industria con respecto al valor del código limpio y simple para el código SIMD.

rwong
fuente
2
¿Tiene requisitos de rendimiento? ¿Puede cumplir con sus requisitos de rendimiento sin usar SIMD? Si no, la pregunta es discutible.
Charles E. Grant
44
Esto es demasiado largo para una pregunta, muy probablemente porque una buena parte de él es efectivamente un intento de responder la pregunta, y mucho tiempo incluso para una respuesta (en parte porque toca muchos más aspectos que la mayoría de las respuestas razonables).
3
Me gusta tener tanto el código limpio / simple / lento (para la prueba inicial del concepto y para fines de documentación posteriores) además de la / s alternativa / s optimizada. Esto hace que sea fácil de entender (ya que las personas pueden leer el código limpio / simple / lento) y fácil de verificar (comparando la versión optimizada con la versión limpia / simple / lenta manualmente y en pruebas unitarias)
Brendan
2
@Brendan He estado en un proyecto similar y he usado un enfoque de prueba con código simple / lento. Si bien es una opción que vale la pena considerar, también tiene limitaciones. Primero, la diferencia de rendimiento puede resultar prohibitiva: las pruebas que utilizan código no optimizado pueden ejecutarse durante horas ... días. En segundo lugar, para el procesamiento de la imagen que puede resultar que poco a poco la comparación simplemente no va a funcionar, cuando el código optimizado produce resultados ligeramente diferentes - de modo que uno tendría que usar la comparación más sofisticados, como la raíz cuadrada media ef diff
mosquito
2
Estoy votando para cerrar esta pregunta como fuera de tema porque no es un problema de programación conceptual como se describe en el centro de ayuda .
durron597

Respuestas:

6

No escribí mucho código SIMD para mí, pero sí mucho código de ensamblador hace algunas décadas. AFAIK que usa intrínsecos SIMD es esencialmente una programación de ensamblador, y toda su pregunta podría reformularse simplemente reemplazando "SIMD" por la palabra "ensamblaje". Por ejemplo, los puntos que ya mencionaste, como

  • el código tarda de 10x a 100x en desarrollarse que el "código de alto nivel"

  • está vinculado a una arquitectura específica

  • el código nunca es "limpio" ni fácil de refactorizar

  • necesitas expertos para escribir y mantenerlo

  • depurar y mantener es difícil, evoluciona muy duro

de ninguna manera son "especiales" para SIMD: estos puntos son ciertos para cualquier tipo de lenguaje ensamblador, y todos son "consenso de la industria". Y la conclusión en la industria del software también es más o menos la misma que para el ensamblador:

  • no lo escriba si no es necesario: use un lenguaje de alto nivel siempre que sea posible y deje que los compiladores hagan el trabajo duro

  • Si los compiladores no son suficientes, encapsule al menos las partes de "bajo nivel" en algunas bibliotecas, pero evite distribuir el código por todo el programa.

  • Como es casi imposible escribir un ensamblador "autodocumentado" o un código SIMD, intente equilibrar esto con mucha documentación.

Por supuesto, existe una diferencia en la situación con el ensamblaje "clásico" o el código de máquina: hoy en día, los compiladores modernos suelen producir código de máquina de alta calidad a partir de un lenguaje de alto nivel, que a menudo se optimiza mejor que el código de ensamblador escrito manualmente. Para las arquitecturas SIMD que son populares hoy en día, la calidad de los compiladores disponibles es AFAIK muy por debajo de eso, y tal vez nunca llegue a eso, ya que la vectorización automática sigue siendo un tema de investigación científica. Ver, por ejemplo, este artículo que describe las diferencias en la optimización entre un compilador y un humano, dando la idea de que podría ser muy difícil crear buenos compiladores SIMD.

Como ya describió en su pregunta, también existe un problema de calidad con las bibliotecas actuales de última generación. Entonces, en mi humilde opinión, lo mejor que podemos esperar es que en los próximos años la calidad de los compiladores y las bibliotecas aumente, tal vez el hardware SIMD tenga que cambiar para ser más "compilador", tal vez lenguajes de programación especializados que admitan una vectorización más fácil (como Halide, que que mencionaste dos veces) se volverá más popular (¿no era ya una fortaleza de Fortran?). Según Wikipedia , SIMD se convirtió en "un producto en masa" hace unos 15 a 20 años (y Halide tiene menos de 3 años, cuando interpreto los documentos correctamente). Compare esto con los compiladores de tiempo para el lenguaje ensamblador "clásico" necesario para madurar. De acuerdo con este artículo de WikipediaPasaron casi 30 años (desde ~ 1970 hasta finales de la década de 1990) hasta que los compiladores excedieron el desempeño de los expertos humanos (en la producción de código máquina no paralelo). Por lo tanto, es posible que tengamos que esperar más de 10 a 15 años hasta que ocurra lo mismo con los compiladores habilitados para SIMD.

Doc Brown
fuente
Según mi lectura del artículo de Wikipedia , parece que hay un general consenso de la industria que el código optimizado a bajo nivel es "considerado difícil de usar, debido a los numerosos detalles técnicos que deben ser recordadas"
mosquito
@gnat: sí, absolutamente, pero creo que si agrego esto a mi respuesta, debería hacer una docena de otras cosas ya mencionadas por el OP en otras palabras en su pregunta demasiado larga.
Doc Brown
de acuerdo, el análisis de su respuesta parece lo suficientemente bueno como es, agregando que la referencia podría llevar a un riesgo de "sobrecarga" que
mosquito
4

Mi organización se ha ocupado de este problema exacto. Nuestros productos están en el espacio de video, pero gran parte del código que escribimos es procesamiento de imágenes que también funcionaría para imágenes fijas.

"Resolvimos" (o tal vez "resolvimos") el problema escribiendo nuestro propio compilador. Esto no es tan loco como parece al principio. Tiene un conjunto restringido de entradas. Sabemos que todo el código está trabajando en imágenes, principalmente imágenes RGBA. Establecemos algunas restricciones, como que los búferes de entrada y salida nunca pueden superponerse, por lo que no hay alias de puntero. Ese tipo de cosas.

Luego escribimos nuestro código en OpenGL Shading Language (glsl). Se compila en código escalar, SSE, SSE2, SSE3, AVX, Neon y, por supuesto, glsl real. Cuando necesitamos soportar una nueva plataforma, actualizamos el compilador al código de salida para esa plataforma.

También hacemos mosaico de las imágenes para mejorar la coherencia de caché, y cosas así. Pero al mantener el procesamiento de imágenes en un kernel pequeño y al usar glsl (que ni siquiera admite punteros), reducimos en gran medida la complejidad de compilar el código.

Este enfoque no es para todos y tiene sus propios problemas (por ejemplo, debe garantizar la corrección del compilador). Pero nos ha funcionado bastante bien.

usuario1118321
fuente
Esto suena 🔥🔥! ¿Este producto que vende o pone a disposición de forma independiente? (Además, ¿es 'AVC' = AVX?)
Ahmed Fasih
Lo siento, sí, me refería a AVX (lo arreglaré). Actualmente no vendemos el compilador como un producto independiente, aunque podría suceder en el futuro.
user1118321
No es broma, esto suena muy bien. Lo más parecido que he visto así es cómo el compilador CUDA solía ser capaz de hacer programas "seriales" que se ejecutan en la CPU para la depuración; esperábamos que eso se generalizara en una forma de escribir código de CPU de subprocesos múltiples y SIMD, pero Pobre de mí. La siguiente cosa más cercana en la que puedo pensar es OpenCL: ¿evaluaron OpenCL y la encontraron inferior a su compilador GLSL para todos?
Ahmed Fasih
1
Bueno, OpenCL no existía cuando empezamos, no creo. (O si lo hizo, era bastante nuevo). Así que realmente no entró en la ecuación.
user1118321
0

No parece agregar demasiada sobrecarga de mantenimiento si considera usar un lenguaje de nivel superior:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

vs

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

Por supuesto, tendrá que enfrentar las limitaciones de la biblioteca, pero no la mantendrá usted mismo. Podría haber un buen equilibrio entre el costo de mantenimiento y el rendimiento ganado.

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx

Guarida
fuente
Según mi lectura, la opción de usar bibliotecas externas ya ha sido investigada y abordada por el autor de la pregunta: "Las bibliotecas con un uso comercial generalizado no parecen estar muy habilitadas para SIMD ..."
gnat
@gnat De hecho, he leído todo el párrafo, no solo las viñetas de nivel superior, y el póster no menciona ninguna biblioteca SIMD de uso general, solo la visión por computadora y el procesamiento de imágenes. Sin mencionar que falta completamente el análisis de la aplicación de lenguajes de nivel superior, a pesar de que no hay una etiqueta C ++ ni especificidad C ++ reflejada en el título de la pregunta. Esto me lleva a creer que si bien mi pregunta no se considerará primaria, es probable que agregue valor, haciendo que las personas conozcan otras opciones.
Den
1
A mi entender, el OP pregunta si existen soluciones con un uso comercial generalizado. Aunque aprecio su sugerencia (tal vez pueda usar la lib para un proyecto aquí), por lo que veo, RyuJIT está lejos de ser un "estándar de la industria ampliamente aceptado".
Doc Brown
@DocBrown tal vez, pero su pregunta real está formulada para ser más genérica: "... consenso de la industria con respecto al valor del código limpio y simple para el código SIMD ...". Dudo que haya algún consenso (oficial), pero afirmo que los lenguajes de nivel superior pueden reducir la diferencia entre el código "habitual" y el SIMD, al igual que C ++, nos olvidamos del ensamblaje, lo que reduce los costos de mantenimiento.
Den
-1

He hecho programación de ensamblaje en el pasado, no programación SIMD recientemente.

¿Has considerado usar un compilador compatible con SIMD como Intel? ¿Es interesante una guía de vectorización con compiladores Intel® C ++ ?

Varios de sus comentarios, como "reventar globos", sugieren utilizar un compilador (para obtener beneficios si no tiene un solo punto de acceso).

ChrisW
fuente
Según mi lectura, este enfoque fue juzgado por autor de la pregunta, consulte las menciones de errores del compilador / defectos en la pregunta
mosquito
El OP no dijo si habían probado el compilador Intel , que también es el tema de este tema de Programmers.SE . La mayoría de la gente no lo ha intentado. No es para todos; pero podría adaptarse al negocio / pregunta del OP (mejor rendimiento para un menor costo de codificación / diseño / mantenimiento).
ChrisW
así lo que leo en la pregunta sugiere que es autor de la pregunta conscientes de los compiladores de Intel y otras arquitecturas: "Algunas arquitecturas mantener la compatibilidad perfecta (Intel), algunos están a la altura ..."
mosquito
"Intel" en esa oración significa el diseñador de chips de Intel, no el escritor de compiladores de Intel.
ChrisW