Esta pergunta é uma extensão de duas discussões que surgiram recentemente nas respostas a " C ++ vs Fortran for HPC ". E é um pouco mais um desafio do que uma pergunta ...
Um dos argumentos mais ouvidos em favor do Fortran é que os compiladores são apenas melhores. Como a maioria dos compiladores C / Fortran compartilham o mesmo back-end, o código gerado para programas semanticamente equivalentes nos dois idiomas deve ser idêntico. Pode-se argumentar, no entanto, que o C / Fortran é mais / menos fácil para o compilador otimizar.
Então decidi tentar um teste simples: peguei uma cópia do daxpy.f e doxpy.c e os compilei com gfortran / gcc.
Agora, o daxpy.c é apenas uma tradução f2c do daxpy.f (código gerado automaticamente, feio como o inferno), então peguei esse código e o limpei um pouco (conheça daxpy_c), o que basicamente significava reescrever o loop mais interno como
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Por fim, reescrevi (digite daxpy_cvec) usando a sintaxe de vetor do 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];
Observe que eu uso vetores de comprimento 2 (isso é tudo o que o SSE2 permite) e que eu processo dois vetores por vez. Isso ocorre porque em muitas arquiteturas, podemos ter mais unidades de multiplicação do que elementos vetoriais.
Todos os códigos foram compilados usando o gfortran / gcc versão 4.5 com os sinalizadores "-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-ponteiro -malign-double -fstrict-aliasing". No meu laptop (CPU Intel Core i5, M560, 2,67GHz), obtive a seguinte saída:
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.
Portanto, o código Fortran original leva um pouco mais de 8,1 segundos, a tradução automática leva 10,5 segundos, a ingênua implementação C faz isso em 7.9 e o código explicitamente vetorizado faz isso em 5.6, marginalmente menos.
Isso é Fortran sendo um pouco mais lento que a implementação C ingênua e 50% mais lento que a implementação C vetorizada.
Então, eis a questão: eu sou um programador C nativo e estou bastante confiante de que fiz um bom trabalho nesse código, mas o código Fortran foi tocado pela última vez em 1993 e, portanto, pode estar um pouco desatualizado. Como não me sinto tão confortável em codificar no Fortran quanto os outros aqui, alguém pode fazer um trabalho melhor, ou seja, mais competitivo em comparação com qualquer uma das duas versões C?
Além disso, alguém pode tentar este teste com icc / ifort? A sintaxe do vetor provavelmente não funcionará, mas eu ficaria curioso para ver como a versão C ingênua se comporta lá. O mesmo vale para qualquer pessoa com xlc / xlf por aí.
Fiz upload das fontes e de um Makefile aqui . Para obter tempos precisos, defina CPU_TPS em test.c com o número de Hz na sua CPU. Se você encontrar melhorias em qualquer uma das versões, poste-as aqui!
Atualizar:
Adicionei o código de teste do stali aos arquivos online e o completei com uma versão em C. Modifiquei os programas para fazer 1'000'000 loops em vetores de comprimento 10'000 para serem consistentes com o teste anterior (e porque minha máquina não pôde alocar vetores de comprimento 1'000'000'000, como no original de stali código). Como os números agora são um pouco menores, usei a opção -par-threshold:50
para tornar o compilador mais propenso a paralelizar. A versão icc / ifort usada é 12.1.2 20111128 e os resultados são os seguintes
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
Em resumo, os resultados são, para todos os efeitos práticos, idênticos para as versões C e Fortran, e ambos os códigos são paralelos automaticamente. Observe que os tempos rápidos comparados ao teste anterior são devidos ao uso de aritmética de ponto flutuante de precisão única!
Atualizar:
Embora eu realmente não goste de onde está indo o ônus da prova aqui, recodifiquei o exemplo de multiplicação de matrizes de stali em C e o adicionei aos arquivos na web . Aqui estão os resultados do loop tripple para uma e duas CPUs:
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
Observe que, cpu_time
no Fortran, mede o tempo da CPU e não o tempo do relógio de parede, por isso encerrei as chamadas time
para compará-las para 2 CPUs. Não há diferença real entre os resultados, exceto que a versão C se sai um pouco melhor em dois núcleos.
Agora, para o matmul
comando, é claro apenas no Fortran, pois esse intrínseco não está disponível em 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
Uau. Isso é absolutamente terrível. Alguém pode descobrir o que estou fazendo de errado ou explicar por que esse intrínseco ainda é de alguma forma uma coisa boa?
Não adicionei as dgemm
chamadas ao benchmark, pois são chamadas de biblioteca para a mesma função no Intel MKL.
Para testes futuros, alguém pode sugerir um exemplo conhecido por ser mais lento em C do que em Fortran?
Atualizar
Para verificar a afirmação de stali de que o matmul
intrínseco é "uma ordem de grandeza" mais rápido que o produto de matriz explícita em matrizes menores, modifiquei seu próprio código para multiplicar matrizes de tamanho 100x100 usando os dois métodos, 10.000 vezes cada. Os resultados, em uma e duas CPUs, são os seguintes:
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
Atualizar
O Grisu está correto ao apontar que, sem otimizações, o gcc converte operações em números complexos em chamadas de função de biblioteca, enquanto o gfortran as alinha em algumas instruções.
O compilador C gerará o mesmo código compacto se a opção -fcx-limited-range
estiver configurada, ou seja, o compilador é instruído a ignorar o excesso / subfluxo potencial nos valores intermediários. Esta opção é de alguma forma definida por padrão no gfortran e pode levar a resultados incorretos. Forçar o -fno-cx-limited-range
gfortran não mudou nada.
Portanto, esse é realmente um argumento contra o uso de gfortran para cálculos numéricos: operações em valores complexos podem exceder ou estourar mesmo que os resultados corretos estejam dentro do intervalo de ponto flutuante. Este é realmente um padrão Fortran. No gcc ou no C99 em geral, o padrão é fazer as coisas estritamente (leia IEEE-754), a menos que especificado de outra forma.
Lembrete: Lembre-se de que a principal questão era se os compiladores Fortran produzem código melhor que os compiladores C. Este não é o lugar para discussões quanto aos méritos gerais de um idioma em detrimento de outro. O que eu realmente estaria interessado é se alguém puder encontrar uma maneira de convencer o gfortran a produzir um daxpy tão eficiente quanto o do C usando vetorização explícita, pois isso exemplifica os problemas de ter que confiar no compilador exclusivamente para otimização do SIMD ou um caso em que um compilador Fortran supera sua contraparte C.