Esta pregunta es una extensión de dos discusiones que surgieron recientemente en las respuestas a " C ++ vs Fortran for HPC ". Y es un poco más un desafío que una pregunta ...
Uno de los argumentos más escuchados a favor de Fortran es que los compiladores son simplemente mejores. Como la mayoría de los compiladores de C / Fortran comparten el mismo back-end, el código generado para programas semánticamente equivalentes en ambos idiomas debe ser idéntico. Sin embargo, se podría argumentar que C / Fortran es más / menos fácil de optimizar para el compilador.
Así que decidí probar una prueba simple: obtuve una copia de daxpy.f y daxpy.c y los compilé con gfortran / gcc.
Ahora daxpy.c es solo una traducción f2c de daxpy.f (código generado automáticamente, feo como diablos), así que tomé ese código y lo limpié un poco (conozca daxpy_c), lo que básicamente significaba volver a escribir el bucle más interno como
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Finalmente, lo reescribí (ingrese daxpy_cvec) usando la sintaxis vectorial de gcc:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
Tenga en cuenta que uso vectores de longitud 2 (eso es todo lo que permite SSE2) y que proceso dos vectores a la vez. Esto se debe a que en muchas arquitecturas, podemos tener más unidades de multiplicación que elementos vectoriales.
Todos los códigos se compilaron usando gfortran / gcc versión 4.5 con las marcas "-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing". En mi computadora portátil (CPU Intel Core i5, M560, 2.67GHz) obtuve la siguiente salida:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
Entonces, el código Fortran original lleva un poco más de 8.1 segundos, la traducción automática del mismo toma 10.5 segundos, la implementación ingenua de C lo hace en 7.9 y el código explícitamente vectorizado lo hace en 5.6, marginalmente menos.
Eso es Fortran siendo un poco más lento que la implementación de C ingenua y 50% más lento que la implementación de C vectorizada.
Así que aquí está la pregunta: soy un programador nativo de C y estoy bastante seguro de que hice un buen trabajo con ese código, pero el código Fortran se tocó por última vez en 1993 y, por lo tanto, podría estar un poco desactualizado. Dado que no me siento tan cómodo codificando en Fortran como otros aquí, ¿alguien puede hacer un mejor trabajo, es decir, más competitivo en comparación con cualquiera de las dos versiones C?
Además, ¿alguien puede probar esta prueba con icc / ifort? La sintaxis vectorial probablemente no funcionará, pero me gustaría ver cómo se comporta allí la ingenua versión C. Lo mismo ocurre con cualquiera con xlc / xlf por ahí.
He subido las fuentes y un Makefile aquí . Para obtener tiempos precisos, configure CPU_TPS en test.c a la cantidad de Hz en su CPU. Si encuentra alguna mejora en alguna de las versiones, ¡publíquela aquí!
Actualizar:
Agregué el código de prueba de stali a los archivos en línea y lo completé con una versión C. Modifiqué los programas para hacer 1'000'000 bucles en vectores de longitud 10'000 para que sean consistentes con la prueba anterior (y porque mi máquina no podía asignar vectores de longitud 1'000'000'000, como en el original de stali código). Como los números ahora son un poco más pequeños, utilicé la opción -par-threshold:50
para que el compilador sea más propenso a paralelizarse. La versión icc / ifort utilizada es 12.1.2 20111128 y los resultados son los siguientes
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
En resumen, los resultados son, a todos los efectos prácticos, idénticos para las versiones C y Fortran, y ambos códigos se paralelizan automáticamente. ¡Tenga en cuenta que los tiempos rápidos en comparación con la prueba anterior se deben al uso de la aritmética de coma flotante de precisión simple!
Actualizar:
Aunque realmente no me gusta a dónde va la carga de la prueba aquí, he vuelto a codificar el ejemplo de multiplicación de matriz de stali en C y lo agregué a los archivos en la web . Estos son los resultados del bucle triple para una y dos CPU:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
Tenga cpu_time
en cuenta que en Fortran mide el tiempo de CPU y no el tiempo del reloj de pared, así que terminé las llamadas time
para compararlas con 2 CPU. No hay una diferencia real entre los resultados, excepto que la versión C funciona un poco mejor en dos núcleos.
Ahora para el matmul
comando, por supuesto solo en Fortran ya que este intrínseco no está disponible en C:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
Guau. Eso es absolutamente terrible. ¿Alguien puede descubrir lo que estoy haciendo mal o explicar por qué este intrínseco sigue siendo algo bueno?
No agregué las dgemm
llamadas al punto de referencia, ya que son llamadas de biblioteca a la misma función en el Intel MKL.
Para las pruebas futuras, puede alguien sugerir un ejemplo conocido a ser más lenta en C que en Fortran?
Actualizar
Para verificar la afirmación de stali de que lo matmul
intrínseco es "un orden de magnitud" más rápido que el producto de matriz explícito en matrices más pequeñas, modifiqué su propio código para multiplicar matrices de tamaño 100x100 usando ambos métodos, 10'000 veces cada una. Los resultados, en una y dos CPU, son los siguientes:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
Actualizar
Grisu tiene razón al señalar que, sin optimizaciones, gcc convierte las operaciones en números complejos en llamadas a funciones de la biblioteca, mientras que gfortran las incluye en unas pocas instrucciones.
El compilador de C generará el mismo código compacto si la opción -fcx-limited-range
está configurada, es decir, se le indica al compilador que ignore los posibles sobre / subflujos en los valores intermedios. Esta opción está configurada de forma predeterminada en gfortran y puede dar lugar a resultados incorrectos. Forzar -fno-cx-limited-range
en gfortran no cambió nada.
Por lo tanto, este es en realidad un argumento en contra del uso de gfortran para cálculos numéricos: las operaciones con valores complejos pueden desbordarse / desbordarse incluso si los resultados correctos están dentro del rango de punto flotante. Este es en realidad un estándar de Fortran. En gcc, o en C99 en general, el valor predeterminado es hacer las cosas estrictamente (leer IEEE-754 compatible) a menos que se especifique lo contrario.
Recordatorio: tenga en cuenta que la pregunta principal era si los compiladores de Fortran producen un código mejor que los compiladores de C. Este no es el lugar para discusiones sobre los méritos generales de un idioma sobre otro. Lo que realmente me interesaría es si alguien puede encontrar una manera de convencer a gfortran para que produzca un daxpy tan eficiente como el de C usando la vectorización explícita, ya que esto ejemplifica los problemas de tener que confiar en el compilador exclusivamente para la optimización SIMD, o un caso en el que un compilador Fortran supera a su contraparte C.