O software científico não é muito diferente de outro software, na medida em que sabe o que precisa de ajuste.
O método que eu uso é uma pausa aleatória . Aqui estão algumas das acelerações que ele encontrou para mim:
Se uma grande fração do tempo é gasta em funções como log
e exp
, posso ver quais são os argumentos para essas funções, em função dos pontos dos quais eles estão sendo chamados. Muitas vezes, eles são chamados repetidamente com o mesmo argumento. Nesse caso, a memorização produz um enorme fator de aceleração.
Se eu estiver usando as funções BLAS ou LAPACK, posso achar que uma grande fração do tempo é gasta em rotinas para copiar matrizes, multiplicar matrizes, transformar choleski, etc.
A rotina para copiar matrizes não existe para velocidade, mas para conveniência. Você pode achar que há uma maneira menos conveniente, mas mais rápida, de fazer isso.
Rotinas para multiplicar ou inverter matrizes ou realizar transformações de choleski tendem a ter argumentos de caracteres que especificam opções, como 'U' ou 'L' para o triângulo superior ou inferior. Mais uma vez, existem por conveniência. O que descobri foi que, como minhas matrizes não eram muito grandes, as rotinas passavam mais da metade do tempo chamando a sub-rotina para comparar caracteres apenas para decifrar as opções. Escrever versões especiais das rotinas matemáticas mais caras produziu aceleração maciça.
Se eu puder apenas expandir o último: a rotina de multiplicação de matriz DGEMM chama LSAME para decodificar seus argumentos de caractere. A análise de perfis de tempo percentual inclusivo (a única estatística que vale a pena considerar) considerada "boa" poderia mostrar o DGEMM usando alguma porcentagem do tempo total, como 80%, e o LSAME usando alguma porcentagem do tempo total, como 50%. Olhando para o primeiro, você seria tentado a dizer "bem, ele deve ser fortemente otimizado, para que eu não possa fazer muito a respeito". Olhando para o último, você seria tentado a dizer "Huh? O que é isso tudo? Isso é apenas uma pequena rotina. Esse criador de perfil deve estar errado!"
Não está errado, apenas não está dizendo o que você precisa saber. O que a pausa aleatória mostra é que o DGEMM está em 80% das amostras de pilha e o LSAME está em 50%. (Você não precisa de muitas amostras para detectar isso. 10 é geralmente suficiente.) Além disso, em muitas dessas amostras, o DGEMM está no processo de chamar o LSAME de algumas linhas de código diferentes.
Então agora você sabe por que as duas rotinas estão demorando tanto. Você também sabe de onde eles estão sendo chamados no código para gastar todo esse tempo. É por isso que uso uma pausa aleatória e tomo uma visão icterícia dos criadores de perfil, não importa quão bem feitos eles sejam. Eles estão mais interessados em obter medições do que em dizer o que está acontecendo.
É fácil supor que as rotinas da biblioteca matemática tenham sido otimizadas até o enésimo grau, mas na verdade elas foram otimizadas para serem usadas para uma ampla variedade de propósitos. Você precisa ver o que realmente está acontecendo, não o que é fácil de assumir.
ADICIONADO: para responder às suas duas últimas perguntas:
Quais são as coisas mais importantes para experimentar primeiro?
Colete amostras de 10 a 20 pilhas e não apenas as resuma, entenda o que cada uma delas está lhe dizendo. Faça isso primeiro, último e intermediário. (Não há "tentativa", jovem Skywalker.)
Como sei quanto desempenho posso obter?
As amostras de pilha fornecerão uma estimativa muito aproximada de qual fração de tempo será salva. (Segue uma distribuição , onde é o número de amostras que exibiram o que você vai corrigir e é o número total de amostras. Isso não conta o custo do código que você usou para substituí-lo, o que provavelmente será pequeno.) Então a taxa de aceleração é de que pode ser grande. Observe como isso se comporta matematicamente. Se e , a média e o modo de são 0,5, para uma taxa de aceleração de 2. Aqui está a distribuição:
Se você é avesso ao risco, sim, há uma pequena probabilidade (0,03%) estexβ(s+1,(n−s)+1)sn1/(1−x)n=10s=5x
x é menor que 0,1, para uma aceleração menor que 11%. Mas o balanceamento é uma probabilidade igual de que é maior que 0,9, para uma taxa de aceleração maior que 10! Se você está recebendo dinheiro proporcionalmente à velocidade do programa, não há chances ruins.x
Como já mencionei antes, você pode repetir todo o procedimento até não poder mais, e a taxa de aceleração composta pode ser bastante grande.
ADICIONADO: Em resposta à preocupação de Pedro sobre falsos positivos, deixe-me tentar construir um exemplo onde eles podem ocorrer. Como nunca agimos sobre um problema em potencial, a menos que o vejamos duas ou mais vezes, esperamos que ocorram falsos positivos quando vemos um problema o menor número possível de vezes, especialmente quando o número total de amostras é grande. Suponha que tomemos 20 amostras e a vejamos duas vezes. Isso estima que seu custo seja 10% do tempo total de execução, o modo de sua distribuição. (A média da distribuição é mais alta - é .) A curva mais baixa no gráfico a seguir é sua distribuição:(s+1)/(n+2)=3/22=13.6%
Considere se pegamos até 40 amostras (mais do que eu já tive ao mesmo tempo) e só vi um problema em duas delas. O custo estimado (modo) desse problema é de 5%, conforme mostrado na curva mais alta.
O que é um "falso positivo"? É que, se você corrige um problema, obtém um ganho tão menor que o esperado, que se arrepende de ter corrigido. As curvas mostram (se o problema for "pequeno") que, embora o ganho possa ser menor que a fração das amostras que o mostram, em média será maior.
Existe um risco muito mais sério - um "falso negativo". É quando há um problema, mas não é encontrado. (Contribuir para isso é o "viés de confirmação", onde a ausência de evidência tende a ser tratada como evidência de ausência.)
O que você obtém com um criador de perfil (bom) é que você obtém uma medição muito mais precisa (portanto, menos chance de falsos positivos), à custa de informações muito menos precisas sobre qual é realmente o problema (menos chance de encontrá-lo e obtê-lo) qualquer ganho). Isso limita a aceleração geral que pode ser alcançada.
Eu encorajaria os usuários de criadores de perfil a relatar os fatores de aceleração que eles realmente praticam.
Há outro ponto a ser ressaltado. A pergunta de Pedro sobre falsos positivos.
Ele mencionou que pode haver uma dificuldade ao se resolver pequenos problemas no código altamente otimizado. (Para mim, um pequeno problema é responsável por 5% ou menos do tempo total.)
Como é perfeitamente possível construir um programa totalmente ideal, exceto 5%, esse ponto só pode ser abordado empiricamente, como nesta resposta . Para generalizar a partir da experiência empírica, é assim:
Um programa, como escrito, normalmente contém várias oportunidades de otimização. (Podemos chamá-los de "problemas", mas geralmente são um código perfeitamente bom, simplesmente capaz de melhorar consideravelmente.) Este diagrama ilustra um programa artificial que leva algum tempo (100s, digamos) e contém os problemas A, B, C, ... que, quando encontrados e corrigidos, economizem 30%, 21% etc. dos 100s originais.
Observe que o problema F custa 5% do tempo original, portanto é "pequeno" e difícil de encontrar sem 40 ou mais amostras.
No entanto, as 10 primeiras amostras encontram facilmente o problema A. ** Quando isso é corrigido, o programa leva apenas 70s, para uma aceleração de 100/70 = 1,43x. Isso não apenas torna o programa mais rápido, como também amplia, nessa proporção, as porcentagens tomadas pelos problemas restantes. Por exemplo, o problema B originalmente levou 21s, o que representava 21% do total, mas após a remoção de A, B tirou 21s dos 70s ou 30%, portanto, é mais fácil encontrar quando todo o processo é repetido.
Depois que o processo é repetido cinco vezes, agora o tempo de execução é de 16,8 segundos, dos quais o problema F é 30%, e não 5%, então 10 amostras o encontram facilmente.
Então esse é o ponto. Empiricamente, os programas contêm uma série de problemas com uma distribuição de tamanhos, e qualquer problema encontrado e corrigido facilita a localização dos demais. Para conseguir isso, nenhum dos problemas pode ser ignorado porque, se houver, eles ficam lá, demorando um tempo, limitando a aceleração total e deixando de ampliar os problemas restantes.
É por isso que é muito importante encontrar os problemas que estão ocultos .
Se os problemas de A a F forem encontrados e corrigidos, a aceleração é de 100 / 11,8 = 8,5x. Se um deles faltar, por exemplo D, a aceleração é de apenas 100 / (11,8 + 10,3) = 4,5x.
Esse é o preço pago por falsos negativos.
Portanto, quando o criador de perfil diz "não parece haver nenhum problema significativo aqui" (ou seja, bom codificador, esse é praticamente o código ideal), talvez esteja certo e talvez não. (Um falso negativo .) Você não sabe ao certo se há mais problemas a serem corrigidos, para maior velocidade, a menos que tente outro método de criação de perfil e descubra que existem. Na minha experiência, o método de criação de perfil não precisa de um grande número de amostras, resumido, mas de um pequeno número de amostras, em que cada amostra é compreendida completamente o suficiente para reconhecer qualquer oportunidade de otimização.
** São necessários no mínimo 2 acertos em um problema para encontrá-lo, a menos que se tenha conhecimento prévio de que existe um loop infinito (quase). (As marcas vermelhas representam 10 amostras aleatórias); O número médio de amostras necessárias para obter 2 ou mais ocorrências, quando o problema é de 30%, é ( distribuição binomial negativa ). 10 amostras o acham com 85% de probabilidade, 20 amostras - 99,2% ( distribuição binomial ). Para obter a probabilidade de encontrar o problema, em R, avaliar , por exemplo: .2/0.3=6.671 - pbinom(1, numberOfSamples, sizeOfProblem)
1 - pbinom(1, 20, 0.3) = 0.9923627
ADICIONADO: A fração de tempo economizada, , segue uma distribuição Beta , em que é o número de amostras e é o número que exibe o problema. No entanto, a taxa de aceleração é igual a (assumindo que é salvo), e seria interessante entender a distribuição de . Acontece que segue uma distribuição BetaPrime . Simulei-o com 2 milhões de amostras, chegando a este comportamento:β ( s + 1 , ( n - s ) + 1 ) n s y 1 / ( 1 - x ) x y y - 1xβ(s+1,(n−s)+1)nsy1/(1−x)xyy−1
distribution of speedup
ratio y
s, n 5%-ile 95%-ile mean
2, 2 1.58 59.30 32.36
2, 3 1.33 10.25 4.00
2, 4 1.23 5.28 2.50
2, 5 1.18 3.69 2.00
2,10 1.09 1.89 1.37
2,20 1.04 1.37 1.17
2,40 1.02 1.17 1.08
3, 3 1.90 78.34 42.94
3, 4 1.52 13.10 5.00
3, 5 1.37 6.53 3.00
3,10 1.16 2.29 1.57
3,20 1.07 1.49 1.24
3,40 1.04 1.22 1.11
4, 4 2.22 98.02 52.36
4, 5 1.72 15.95 6.00
4,10 1.25 2.86 1.83
4,20 1.11 1.62 1.31
4,40 1.05 1.26 1.14
5, 5 2.54 117.27 64.29
5,10 1.37 3.69 2.20
5,20 1.15 1.78 1.40
5,40 1.07 1.31 1.17
As duas primeiras colunas fornecem o intervalo de confiança de 90% para a taxa de aceleração. A taxa média de aceleração é igual a exceto no caso em que . Nesse caso, é indefinido e, de fato, à medida que aumenta o número de valores simulados , a média empírica aumenta.s = n y(n+1)/(n−s)s=ny
Este é um gráfico da distribuição dos fatores de aceleração e suas médias para 2 ocorrências em 5, 4, 3 e 2 amostras. Por exemplo, se três amostras forem coletadas e duas delas forem encontradas em um problema, e esse problema puder ser removido, o fator de aceleração médio seria 4x. Se os 2 hits forem vistos em apenas 2 amostras, a aceleração média é indefinida - conceitualmente porque existem programas com loops infinitos com probabilidade diferente de zero!