¿Cuándo debería usar plantillas de expresión C ++ en ciencia computacional y cuándo * no * debería usarlas?

24

Supongamos que estoy trabajando en un código científico en C ++. En una discusión reciente con un colega, se argumentó que las plantillas de expresión podrían ser algo realmente malo, lo que podría hacer que el software sea compilable solo en ciertas versiones de gcc. Supuestamente, este problema ha afectado algunos códigos científicos, como se alude en los subtítulos de esta parodia de Downfall . (Estos son los únicos ejemplos que conozco, de ahí el enlace).

Sin embargo, otras personas han argumentado que las plantillas de expresión son útiles porque pueden generar ganancias de rendimiento, como en este artículo en SIAM Journal of Scientific Computing , al evitar el almacenamiento de resultados intermedios en variables temporales.

No sé mucho acerca de la metaprogramación de plantillas en C ++, pero sí sé que es un enfoque utilizado en la diferenciación automática y en la aritmética de intervalos, que es cómo me metí en una discusión sobre plantillas de expresión. Dadas las ventajas potenciales en el rendimiento y las desventajas potenciales en el mantenimiento (si esa es la palabra correcta), ¿cuándo debo usar plantillas de expresión C ++ en ciencia computacional y cuándo debo evitarlas?

Geoff Oxberry
fuente
Ah, el video es muy gracioso. No sabía que existía. ¿Quién lo hizo, sabes?
Wolfgang Bangerth
Ni idea; un par de personas de PETSc me enviaron enlaces en un momento. Creo que un desarrollador de FEniCS lo hizo.
Geoff Oxberry
El enlace del video está roto y me muero de curiosidad. Nuevo enlace?
Praxeolitic
Oh, diablos, no importa, veo que YouTube ha venido para nuestros videos de Hitler.
Praxeolítico

Respuestas:

17

Mi problema con las plantillas de expresión es que son una abstracción muy permeable. Pasas mucho trabajo escribiendo código muy complicado para hacer una tarea simple con una sintaxis más agradable. Pero si desea cambiar el algoritmo, tiene que meterse con el código sucio y si se desliza con los tipos o la sintaxis, recibirá mensajes de error completamente ininteligibles. Si su aplicación se asigna perfectamente a una biblioteca basada en plantillas de expresión, puede valer la pena considerarla, pero si no está seguro, recomendaría simplemente escribir código normal. Claro, el código de alto nivel es menos bonito, pero puedes hacer lo que hay que hacer. Como beneficio, el tiempo de compilación y los tamaños binarios disminuirán mucho y no tendrá que hacer frente a una gran variación en el rendimiento debido a la elección del compilador y el indicador de compilación.

Jed Brown
fuente
Sí, he visto algunos de los largos mensajes de error de primera mano cuando tuve que transferir el código de gcc 2.95 a gcc 4.x, y el compilador arrojó todo tipo de errores sobre las plantillas. Un compañero de laboratorio mío está desarrollando una biblioteca con plantillas para la aritmética de intervalos en C ++ (agregando nuevas características que no están en Boost :: Interval para lograr más investigación), y no quiero ver que el código se convierta en una pesadilla compilar.
Geoff Oxberry
12

Otros han comentado sobre el problema de lo difícil que es escribir programas ET, así como la complejidad de comprender los mensajes de error. Permítanme comentar sobre el tema de los compiladores: es cierto que hace un tiempo uno de los grandes problemas fue encontrar un compilador que cumpla lo suficiente con el estándar C ++ para que todo funcione y que funcione de manera portátil. Como consecuencia, encontramos muchos errores: tengo 2-300 informes de errores en mi nombre, distribuidos en gcc, Intel icc, IBM xlC y pgicc de Portland. En consecuencia, el script de configuración deal.II es un repositorio de una gran cantidad de pruebas de errores del compilador, principalmente en el área de plantillas, declaraciones de amigos, espacios de nombres, etc.

Pero resulta que los creadores de compiladores realmente han logrado actuar juntos: hoy, gcc e icc hoy pasan todas nuestras pruebas y es fácil escribir código que sea portátil entre los dos. Diría que PGI no está muy lejos, pero tiene una serie de peculiaridades que no parecen desaparecer con los años. xlC, por otro lado, es una historia completamente diferente: corrigen un error cada 6 meses, pero a pesar de presentar informes de errores con ellos durante años, el progreso es extremadamente lento y xlC nunca ha podido compilar el acuerdo.II con éxito.

Lo que todo esto significa es esto: si te quedas con los dos grandes compiladores, puedes esperar que funcionen hoy. Dado que la mayoría de las computadoras y sistemas operativos de hoy en día suelen tener al menos uno de ellos, es suficiente. La única plataforma donde las cosas son más difíciles es BlueGene, donde el compilador del sistema generalmente es xlC, con todos sus errores.

Wolfgang Bangerth
fuente
Solo por curiosidad, ¿has intentado compilar contra los nuevos compiladores xlc en / Q?
Aron Ahmadia
No. Admitiré que he renunciado a xlC.
Wolfgang Bangerth
5

He experimentado un poco con ET's hace mucho tiempo cuando, como mencionaste, los compiladores todavía estaban luchando con ellos. He usado la biblioteca blitz para álgebra lineal en algún código mío. El problema era obtener el buen compilador y, como no soy un programador perfecto de C ++, interpretar los mensajes de error del compilador. Este último era simplemente inmanejable. El compilador generaría, en promedio, alrededor de 1000 líneas de mensajes de error. De ninguna manera pude encontrar rápidamente mi error de programación.

Puede encontrar más información en la página web de oonumerics (están las actas de dos talleres de ET).

Pero me quedaría lejos de ellos ...

GertVdE
fuente
Los mensajes de error del compilador son de hecho una de mis preocupaciones. Con algunos de los códigos de C ++ con plantilla que compilo para construir bibliotecas para mis proyectos, el compilador puede generar cientos de líneas de mensajes de advertencia. Sin embargo, no es mi código, no lo entiendo y, en general, funciona, así que lo dejo solo. Los mensajes de error largos y crípticos no son un buen augurio para la depuración.
Geoff Oxberry
4

El problema ya comienza con el término 'plantillas de expresión (ET)'. No sé si hay una definición precisa para ello. Pero en su uso común, de alguna manera combina "cómo codifica expresiones de álgebra lineal" y "cómo se calcula". Por ejemplo:

Usted codifica la operación del vector

v = 2*x + 3*y + 4*z;                    // (1)

Y se calcula mediante un bucle

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

En mi opinión, estas son dos cosas diferentes y deben desacoplarse: (1) es una interfaz y (2) una posible implementación. Quiero decir, esta es una práctica común en la programación. Claro (2) podría ser una buena implementación predeterminada, pero en general quiero poder utilizar una implementación especializada y dedicada. Por ejemplo, quiero que una función como

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

que me llamen cuando estoy codificando (1). Quizás (3) solo usa internamente un bucle como en (2). Pero dependiendo del tamaño del vector, otras implementaciones podrían ser más eficientes. De todos modos, algunos expertos en alto rendimiento pueden implementar y ajustar (3) tanto como sea posible. Entonces, si (1) no se puede asignar a una llamada de (3), prefiero evitar el azúcar sintáctico de (1) y llamar directamente a (3) de inmediato.

Lo que describo no es nada nuevo. Por el contrario, es la idea detrás de BLAS / LPACK:

  • Todas las operaciones críticas de rendimiento en LAPACK se realizan llamando a las funciones BLAS.
  • BLAS simplemente define una interfaz para esas expresiones de álgebra lineal que comúnmente se necesitan.
  • Para BLAS existen diferentes implementaciones optimizadas.

Si el alcance de BLAS no es suficiente (por ejemplo, no proporciona una función como (3)), entonces se puede ampliar el alcance de BLAS. Entonces, este dinosaurio de los años 60 y 70 se da cuenta con su herramienta de la edad de piedra de una separación limpia y ortogonal de la interfaz y la implementación. Es curioso que (la mayoría) las bibliotecas numéricas de C ++ no alcancen este nivel de calidad de software. Aunque el lenguaje de programación en sí es mucho más sofisticado. Por lo tanto, no sorprende que BLAS / LAPACK siga vivo y desarrollado activamente.

Entonces, en mi opinión, los extraterrestres no son malvados per se. Pero cómo se usan comúnmente en bibliotecas numéricas de C ++ les ganó una muy mala reputación en los círculos de computación científica.

Michael Lehn
fuente
Michael, creo que te falta una de las plantillas de puntos de expresión. Su ejemplo de código (1) en realidad no se asigna a ninguna llamada BLAS optimizada. De hecho, incluso cuando existe una rutina BLAS, la sobrecarga de una llamada de función BLAS hace que sea bastante terrible para pequeños vectores y matrices. Las sofisticadas bibliotecas de plantillas de expresión como Blaze y Eigen pueden usar la evaluación de expresión diferida para evitar el uso de temporarios, pero estoy convencido de que casi nada menos que un lenguaje específico de dominio podrá vencer el álgebra lineal enrollada a mano.
Aron Ahmadia
No, creo que te estás perdiendo el punto. Debe distinguir entre (a) BLAS como una especificación de alguna operación de álgebra lineal que se necesita con frecuencia (b) una implementación de BLAS como ATLAS, GotoBLAS, etc. Por cierto, cómo funciona en FLENS: por defecto, una expresión como (1) ser evaluado llamando a axpy desde BLAS tres veces. Pero sin modificar (1) también podría evaluarlo como en (2). Entonces, lo que sucede lógicamente es lo siguiente: si una operación como en (1) es importante, entonces el conjunto de operaciones BLAS especificadas (a) puede extenderse.
Michael Lehn
Entonces, el punto clave es: la notación como 'v = x + y + z' y cómo finalmente se computa finalmente debe separarse. Eigen, MTL, BLITZ, blaze-lib fallan completamente a este respecto.
Michael Lehn
1
Correcto, pero la cantidad de operaciones de álgebra lineal que se necesitan con frecuencia es combinatoria. Si va a usar un lenguaje como C ++, tiene la opción de implementar las plantillas de expresión según sea necesario (este es el enfoque Eigen / Blaze) combinando subbloques y algoritmos de manera inteligente mediante una evaluación diferida, o implementando una masiva biblioteca de todas las rutinas posibles. No defiendo ninguno de los dos enfoques, ya que el trabajo reciente en Numba y Cython muestra que podemos obtener un rendimiento similar o mejor trabajando con lenguajes de script de alto nivel como Python.
Aron Ahmadia
Pero nuevamente, de lo que me quejo es del hecho de que bibliotecas tan sofisticadas (en el sentido de complicadas pero inflexibles) como Eigen combinan estrechamente la notación y el mecanismo de evaluación e incluso piensan que es algo bueno. Si uso una herramienta como Matlab, solo quiero codificar cosas y confiar en que Matlab está haciendo lo mejor posible. Si uso un lenguaje como C ++, entonces quiero tener el control. Entonces aprecie si existe un mecanismo de evaluación predeterminado pero debe ser posible cambiarlo. De lo contrario, regreso y llamo a las funciones en C ++ directamente.
Michael Lehn