Parece que você deseja uma maneira de avaliar como o seu código está vinculado à FPU ou com que eficácia você está usando a FPU, em vez de contar o número de fracassos de acordo com a mesma definição anacrônica de um "fracasso". Em outras palavras, você deseja uma métrica que atinja o mesmo pico se cada unidade de ponto flutuante estiver funcionando com capacidade total a cada ciclo. Vejamos um Intel Sandy Bridge para ver como isso pode mudar.
Operações de ponto flutuante suportadas por hardware
Este chip suporta instruções AVX , portanto, os registros têm 32 bytes de comprimento (mantendo 4 duplos). A arquitetura superescalar permite que as instruções se sobreponham, com a maioria das instruções aritméticas levando alguns ciclos para serem concluídas, mesmo que uma nova instrução possa iniciar no próximo ciclo. Essas semânticas geralmente são abreviadas pela latência de gravação / taxa de transferência inversa, um valor de 5/2 significaria que a instrução leva 5 ciclos para ser concluída, mas você pode iniciar uma nova instrução a cada dois ciclos (assumindo que os operandos estejam disponíveis, portanto, não há dados dependência e não espera pela memória).
Existem três unidades aritméticas de ponto flutuante por núcleo, mas a terceira não é relevante para a nossa discussão, chamaremos as duas unidades relevantes de A e M porque suas funções principais são adição e multiplicação. Instruções de exemplo (consulte as tabelas de Agner Fog )
vaddpd
: adição compactada, unidade de ocupação A por 1 ciclo, latência / taxa de transferência inversa é 3/1
vmulpd
: multiplicação empacotada, unidade M, 5/1
vmaxpd
: embalado selecione no máximo em pares, unidade A, 3/1
vdivpd
: divisão empacotada, unidade M (e alguns A), 21/20 a 45/44, dependendo da entrada
vsqrtpd
: raiz quadrada compactada, algumas A e M, 21/21 a 43/43, dependendo da entrada
vrsqrtps
: raiz quadrada recíproca de baixa precisão e embalada para entrada única de precisão (8 floats
)
A semântica precisa do que pode se sobrepor vdivpd
e vsqrtpd
é aparentemente sutil e do AFAIK, não está documentada em nenhum lugar. Na maioria dos usos, acho que há pouca possibilidade de sobreposição, embora o texto no manual sugira que vários threads possam oferecer mais possibilidade de sobreposição nesta instrução. Podemos bater pico fracassos se começarmos um vaddpd
e vmulpd
em cada ciclo, para um total de 8-flops por ciclo. Multiplicar matriz-matriz densa ( dgemm
) pode ficar razoavelmente próximo desse pico.
Ao contar os flops para obter instruções especiais, eu veria quanto da FPU está ocupada. Suponha como argumento que, em seu intervalo de entrada, vdivpd
demorou em média 24 ciclos para concluir, ocupando totalmente a unidade M, mas a adição poderia (se disponível) ser executada simultaneamente por metade dos ciclos. A FPU é capaz de realizar 24 multiplicações empacotadas e 24 adições empacotadas durante esses ciclos (perfeitamente intercaladas vaddpd
e vmulpd
), mas com um vdivpd
, o melhor que podemos fazer é 12 adições empacotadas adicionais. Se supusermos que a melhor maneira possível de fazer divisão é usar o hardware (razoável), poderemos contar osvdivpd
36 "flops" compactados, indicando que devemos contar cada divisão escalar como 36 "flops".
Com a raiz quadrada recíproca, às vezes é possível superar o hardware, especialmente se a precisão total não for necessária ou se a faixa de entrada for estreita. Como mencionado acima, a vrsqrtps
instrução é muito barata, portanto (se com precisão única), você pode fazer uma vrsqrtps
seguida por uma ou duas iterações de Newton para limpar. Essas iterações de Newton são apenas
y *= (3 - x*y*y)*0.5;
Se muitas dessas operações precisarem ser realizadas, isso poderá ser significativamente mais rápido do que a avaliação ingênua de y = 1/sqrt(x)
. Antes da disponibilidade do hardware, raiz quadrada recíproca aproximada, algum código sensível ao desempenho usava operações inteiras infames para encontrar uma estimativa inicial da iteração de Newton.
Funções matemáticas fornecidas pela biblioteca
Podemos aplicar uma heurística semelhante às funções matemáticas fornecidas pela biblioteca. Você pode criar um perfil para determinar o número de instruções do SSE, mas, como discutimos, essa não é a história completa, e um programa que gasta todo o tempo avaliando funções especiais pode não parecer próximo do pico, o que pode ser verdade, mas não é. é útil para dizer que todo o tempo é gasto fora de seu controle na FPU.
Sugiro o uso de uma boa biblioteca matemática de vetores como linha de base (por exemplo, o VML da Intel, parte do MKL). Meça o número de ciclos para cada chamada e multiplique pelo pico de falhas possíveis nesse número de ciclos. Portanto, se um exponencial compactado leva 50 ciclos para avaliar, conte-o como 100 flops vezes a largura do registro. Infelizmente, às vezes, é difícil chamar bibliotecas de matemática vetorial e não tem todas as funções especiais; portanto, você pode acabar fazendo matemática escalar; nesse caso, você contaria nossa hipotética exponencial escalar como 100 flops (mesmo que provavelmente ainda precise de 50 ciclos, portanto, você obteria apenas 25% do "pico" se gastar todo o tempo avaliando esses exponenciais).
Como já mencionado, você pode contar ciclos e contadores de eventos de hardware usando PAPI ou várias interfaces. Para uma contagem simples de ciclos, você pode ler o contador de ciclos diretamente, usando a rdtsc
instrução com um trecho de montagem em linha.