Contagem de FLOP para funções de biblioteca


13

Ao avaliar o número de FLOPs em uma função simples, geralmente é possível simplesmente descer a expressão que calcula os operadores aritméticos básicos. No entanto, no caso de declarações matemáticas envolvendo divisão uniforme, não se pode fazer isso e espera poder comparar com contagens de FLOP de funções com apenas adições e multiplicações. A situação é ainda pior quando a operação é implementada em uma biblioteca. Portanto, é imperativo ter alguma noção razoável do desempenho das funções especiais.

Por funções especiais, entendemos coisas como:

  • exp ()
  • sqrt ()
  • sin / cos / tan ()

normalmente fornecidos pelas bibliotecas do sistema.

A determinação da complexidade disso é confundida ainda mais pelo fato de muitos deles serem adaptativos e terem complexidade dependente de entrada. Por exemplo, implementações numericamente estáveis de exp () geralmente adaptam novamente a escala e usam pesquisas. Minha impressão inicial aqui é que o melhor que se pode fazer nesse caso é verificar o comportamento médio das funções.

Toda essa discussão é, obviamente, altamente dependente da arquitetura. Para esta discussão, podemos nos restringir às arquiteturas tradicionais de uso geral e excluir aquelas com unidades de função especiais (GPUs, etc.)

Pode-se encontrar tentativas bastante simples de padronizá- las para arquiteturas específicas em prol da comparação entre sistemas e sistemas, mas isso não é aceitável se se preocupar com o desempenho do método versus o método. Quais metodologias para determinar a complexidade do FLOP dessas funções são consideradas aceitáveis? Existem grandes armadilhas?


Peter, apenas um comentário rápido. Embora você forneça vários bons exemplos de funções fornecidas pelas bibliotecas matemáticas, as divisões de ponto flutuante são normalmente implementadas pela unidade de ponto flutuante.
Aron Ahmadia

Obrigado! Eu não estava suficientemente claro. Acabei de editar para fornecer um melhor contraste.
Peter Brune

Fiquei surpreso ao descobrir que sin, cos e sqrt também são realmente implementados no subconjunto de ponto flutuante x87 das instruções x86. Eu acho que eu entendo seu ponto, mas acho que a prática aceita é apenas para tratá-los como operações de ponto flutuante com constantes ligeiramente maiores :)
Aron Ahmadia

@AronAhmadia Não há uma razão para usar o x87 em mais de uma década. Divida e sqrt()esteja no SSE / AVX, mas leva muito mais tempo que adição e multilização. Além disso, eles são pouco vetorizados no Sandy Bridge AVX, levando o dobro do tempo da instrução SSE (com metade da largura). Por exemplo, o AVX de precisão dupla (4 duplos de largura) pode fazer uma multiplicação empacotada e adicionar empacotados a cada ciclo (assumindo que não haja dependências ou paradas na memória), o que equivale a 8 falhas por ciclo. A divisão leva entre 20 e 44 ciclos para fazer esses "4 flops".
precisa

sqrt () é opcional no PowerPC. Muitos chips incorporados dessa arquitetura não implementam a instrução, por exemplo, série Freescale MPC5xxx.
Damien

Respostas:


10

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 vdivpde 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 vaddpde vmulpdem 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, vdivpddemorou 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 vaddpde 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 vrsqrtpsinstrução é muito barata, portanto (se com precisão única), você pode fazer uma vrsqrtpsseguida 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 rdtscinstrução com um trecho de montagem em linha.


7

Você pode contá-los em sistemas reais usando o PAPI , que concede acesso a contadores de hardware e programas de teste simples. Minha interface / invólucro PAPI favorito é o IPM (Integrated Performance Monitor), mas existem outras soluções ( TAU , por exemplo). Isso deve fornecer uma comparação método a método bastante estável.


4

Vou responder a esta pergunta como se você perguntasse:

"Como comparo ou prevejo analiticamente o desempenho de algoritmos que dependem fortemente de funções especiais, em vez das contagens tradicionais de FLOP de multiplicar-adicionar-transportar que vêm da álgebra linear numérica"

Concordo com a sua primeira premissa, que o desempenho de muitas funções especiais depende da arquitetura e que, embora você possa tratar cada uma dessas funções como tendo custo constante, o tamanho da constante variará, mesmo entre dois processadores da mesma empresa, mas com arquiteturas diferentes (consulte a tabela de tempo das instruções de Agner Fog para referência).

Discordo, porém, que o foco da comparação deve estar nos custos das operações individuais de ponto flutuante. Eu acho que contar FLOPs ainda é, até certo ponto, útil, mas existem várias considerações muito mais importantes que podem tornar o custo de funções especiais menos relevantes ao comparar dois algoritmos em potencial, e eles devem ser examinados explicitamente primeiro antes de se fazer uma comparação de operações de ponto flutuante:

  1. Escalabilidade - Algoritmos que apresentam tarefas que podem ser implementadas com eficiência em arquiteturas paralelas dominarão a arena da computação científica no futuro próximo. Um algoritmo com uma "escalabilidade" melhor, seja por meio de uma comunicação mais baixa, menos necessidade de sincronização ou melhor equilíbrio de carga natural, pode empregar funções especiais mais lentas e, portanto, ser mais lento para pequenos números de processos, mas eventualmente alcançará o número de processadores é aumentado.

  2. Localidade de referência temporal - O algoritmo reutiliza dados entre tarefas, permitindo que o processador evite tráfego de memória desnecessário? Cada nível da hierarquia de memória que um algoritmo percorre adiciona outra ordem de custo de magnitude (aproximadamente) a cada acesso à memória. Como resultado, um algoritmo com alta densidade de operações especiais provavelmente será significativamente mais rápido que um algoritmo com o número equivalente de operações de funções simples em uma região maior da memória.

  3. Pegada na memória - isso está fortemente relacionado aos pontos anteriores, mas à medida que os computadores aumentam cada vez mais, a quantidade de memória por núcleo está na verdade tendendo para baixo. Há dois benefícios em uma pequena área de memória. A primeira é que uma pequena quantidade de dados do programa provavelmente será capaz de caber completamente no cache do processador. A segunda é que, para problemas muito grandes, um algoritmo com menor espaço de memória pode caber na memória do processador, permitindo a solução de problemas que, de outra forma, excederiam a capacidade do computador.


Eu diria que conhecer o FLOPS / s permite separar em qual regime de gargalo (memória, comunicação) você está razoavelmente bem. Por exemplo, considere os métodos de Newton-Krylov, que passam muito tempo fazendo matvecs. Matvecs faz um FLOP ou dois por entrada de matriz e é isso. Irmãs desmontadas têm o potencial de fazer melhor. Jed e eu também conversamos sobre isso, e uma noção alternativa é ver quantos ciclos você está gastando em computação vinculada ao FLOP. No entanto, isso pode exigir um monitoramento bastante refinado, e o total de FLOPS / s pode ser mais prático.
22412 Peter Brune

Aron, a maior parte desta resposta parece contornar a pergunta de Pedro em favor de responder a esta outra pergunta: scicomp.stackexchange.com/questions/114
Jed Brown

@JedBrown, concordo, obrigado por reservar um tempo para reunir uma resposta muito mais sólida.
Aron Ahmadia

0

Por que se preocupar em contar flops? Basta contar os ciclos para cada operação e você terá algo universal.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.