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.
fonte
restrict
palavra - chave que informa exatamente ao compilador: supor que uma matriz não se sobreponha a nenhuma outra estrutura de dados.Respostas:
A diferença nos tempos parece ser devida ao desenrolamento manual do Fortran daxpy . Os seguintes tempos estão em um Xeon X5650 de 2,67 GHz, usando o comando
Compiladores Intel 11.1
Fortran com desenrolamento manual: 8,7 seg
Fortran sem desenrolamento manual: 5,8 seg
C sem desenrolamento manual: 5,8 seg
Compiladores GNU 4.1.2
Fortran com desenrolamento manual: 8,3 seg
Fortran sem desenrolamento manual: 13,5 seg
C sem desenrolamento manual: 13,6 seg
C com atributos vetoriais: 5,8 seg
Compiladores GNU 4.4.5
Fortran com desenrolamento manual: 8,1 seg
Fortran sem desenrolamento manual: 7,4 seg
C sem desenrolamento manual: 8,5 seg
C com atributos vetoriais: 5,8 seg
Conclusões
Hora de testar rotinas mais complicadas, como dgemv e dgemm?
fonte
Estou chegando atrasado para esta festa, por isso é difícil para mim acompanhar o que vem de cima. A questão é grande, e acho que se você estiver interessado, pode ser dividido em pedaços menores. Uma coisa que me interessou foi simplesmente o desempenho de suas
daxpy
variantes e se o Fortran é mais lento que C nesse código muito simples.Executando o meu laptop (Macbook Pro, Intel Core i7, 2,66 GHz), o desempenho relativo da versão C vetorizada à mão e da versão Fortran vetorizada não à mão depende do compilador usado (com suas próprias opções):
Portanto, parece que o GCC ficou melhor em vetorizar o loop no ramo 4.6 do que era antes.
No debate geral, acho que é possível escrever código rápido e otimizado no C e no Fortran, quase como na linguagem assembly. Vou apontar, no entanto, uma coisa: assim como o assembler é mais tedioso para escrever do que C, mas oferece um controle mais refinado sobre o que é executado pela CPU, C é de nível mais baixo que o Fortran. Assim, oferece mais controle sobre os detalhes, o que pode ajudar a otimizar, onde a sintaxe padrão do Fortran (ou suas extensões de fornecedor) pode não ter funcionalidade. Um caso é o uso explícito de tipos de vetores, outro é a possibilidade de especificar o alinhamento de variáveis manualmente, algo que o Fortran é incapaz.
fonte
A maneira como eu escreveria o AXPY no Fortran é um pouco diferente. É a tradução exata da matemática.
m_blas.f90
Agora vamos chamar a rotina acima em um programa.
test.f90
Agora vamos compilar e executá-lo ...
Observe que não estou usando nenhum loop ou nenhuma diretiva explícita do OpenMP . Isso seria possível em C (ou seja, sem uso de loops e paralelismo automático)? Eu não uso C, então não sei.
fonte
icc
também faz paralelização automática. Eu adicionei um arquivoicctest.c
para outras fontes. Você pode compilá-lo com as mesmas opções usadas anteriormente, executá-lo e reportar os horários? Eu tive que adicionar uma declaração printf ao meu código para evitar que o gcc otimizasse tudo. Este é apenas um truque rápido e espero que seja livre de bugs!Eu acho que não é apenas interessante como um compilador otimiza o código para o hardware moderno. Especialmente entre o GNU C e o GNU Fortran, a geração de código pode ser muito diferente.
Então, vamos considerar outro exemplo para mostrar as diferenças entre eles.
Usando números complexos, o compilador GNU C produz uma grande sobrecarga para operações aritméticas quase muito básicas em um número complexo. O compilador Fortran fornece um código muito melhor. Vamos dar uma olhada no pequeno exemplo a seguir no Fortran:
dá (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Que são códigos de máquina de 39 bytes. Quando consideramos o mesmo em C
e dê uma olhada na saída (feita da mesma maneira como acima), obtemos:
Também são códigos de máquina de 39 bytes, mas a função 57 menciona a etapa, faz a parte apropriada do trabalho e executa a operação desejada. Portanto, temos um código de máquina de 27 bytes para executar a operação múltipla. A função por trás do muldc3 é fornecida por
libgcc_s.so
e possui uma área útil de 1375 bytes no código da máquina. Isso diminui drasticamente o código e fornece uma saída interessante ao usar um criador de perfil.Quando implementamos os exemplos BLAS acima para
zaxpy
e realizamos o mesmo teste, o compilador Fortran deve fornecer melhores resultados que o compilador C.(Usei o GCC 4.4.3 para este experimento, mas notei esse comportamento para o qual outro GCC é lançado.)
Então, na minha opinião, não pensamos apenas em paralelização e vetorização quando pensamos em qual é o melhor compilador, também temos que ver como as coisas básicas são traduzidas para o código do assembler. Se essa tradução der código incorreto, a otimização poderá usar essas coisas apenas como entrada.
fonte
complex.c
e o adicionei ao código online. Eu tive que adicionar toda a entrada / saída para garantir que nada seja otimizado. Só recebo uma ligação__muldc3
se não o usar-ffast-math
. Com-O2 -ffast-math
eu recebo 9 linhas de assembler embutido. Você pode confirmar isso?-ffast-math
), você não deve usar o Fortran para seus cálculos de valor complexo. Como descrevi na atualização da minha pergunta,-ffast-math
ou, de maneira mais geral,-fcx-limited-range
obriga o gcc a usar os mesmos cálculos de intervalo restrito que não são IEEE, como são padrão no Fortran. Então se você quer toda a gama de valores complexos e correta Infs e NaNs, você não deve usar Fortran ...Pessoal,
Achei essa discussão muito interessante, mas fiquei surpreso ao ver que reordenar os loops no exemplo de Matmul mudou a imagem. Como não tenho um compilador Intel disponível na minha máquina atual, estou usando o gfortran, mas reescrevendo os loops no mm_test.f90 para
alterei todos os resultados para minha máquina.
Os resultados de temporização da versão anterior foram:
considerando que, com os laços triplos reorganizados como indicado acima:
Este é o gcc / gfortran 4.7.2 20121109 em uma CPU Intel (R) Core (TM) i7-2600K a 3.40GHz
Os sinalizadores do compilador usados foram os do Makefile que cheguei aqui ...
fonte
Não são as linguagens que tornam o código mais rápido, embora ajudem. É o compilador, a CPU e o sistema operacional que torna os códigos executados mais rapidamente. Comparar idiomas é apenas um nome impróprio, inútil e sem sentido. Não faz nenhum sentido, porque você está comparando duas variáveis: a linguagem e o compilador. Se um código for mais rápido, você não sabe quanto é a linguagem ou quanto é o compilador. Eu não entendo por que a comunidade de ciência da computação simplesmente não entende isso :-(
fonte