Olá pessoal. Hoje, queremos compartilhar outra tradução preparada antes do lançamento do curso "Desenvolvedor Web em Python" . Vamos lá!

Fiquei muito surpreso recentemente quando descobri que
>>> pow(3,89)
trabalha mais devagar que
>>> 3**89
Tentei encontrar uma explicação aceitável, mas não consegui. Acompanhei o tempo de execução dessas duas expressões usando o módulo timeit do Python 3:
$ python3 -m timeit 'pow(3,89)' 500000 loops, best of 5: 688 nsec per loop $ python3 -m timeit '3**89' 500000 loops, best of 5: 519 nsec per loop
A diferença é pequena. Apenas 0,1 μs, mas isso me assombrou. Se não consigo explicar nada na programação, começo a sofrer de insônia
Encontrei a resposta usando o feed de IRC do Python no Freenode. O motivo pelo qual o pow funciona um pouco mais devagar é porque o CPython já possui uma etapa extra para carregar o pow a partir do namespace. Enquanto que ao chamar 3 ** 9, essa carga não é necessária em princípio. Isso também significa que essa diferença de tempo permanecerá mais ou menos constante se os valores de entrada aumentarem.
A hipótese foi confirmada:
$ python3 -m timeit 'pow(3,9999)' 5000 loops, best of 5: 58.5 usec per loop $ python3 -m timeit '3**9999' 5000 loops, best of 5: 57.3 usec per loop
No processo de encontrar uma solução para esse problema, também aprendi sobre o módulo dis. Ele permite descompilar o bytecode do Python e aprendê-lo. Foi uma descoberta extremamente empolgante, pois recentemente estudei engenharia reversa de arquivos binários, e o módulo descoberto foi útil nesse assunto.
Descompilei o bytecode das expressões acima e obtive o seguinte:
>>> import dis >>> dis.dis('pow(3,89)')
Você pode ler sobre como entender corretamente a saída do dis.dis consultando esta resposta no Stackoverflow.
Ok, voltando ao código. Descompilar o pow faz sentido. O bytecode carrega pow do espaço de nomes, carrega nos registradores 3 e 89 e, finalmente, chama a função pow. Mas por que os resultados das próximas duas descompilações são diferentes? Afinal, tudo o que mudamos é o valor do expoente de 64 para 65!
Esta pergunta me apresentou outro novo conceito chamado "convolução de constantes". Isso significa que, quando temos uma expressão constante, o Python calcula seu valor no estágio de compilação; portanto, quando você executa o programa, não leva muito tempo, porque o Python usa o valor já calculado. Dê uma olhada nisso:
def one_plue_one(): return 1+1
Python compila a primeira função na segunda e a usa ao executar o código. Nada mal, né?
Então, por que a convolução de constantes funciona para 3 ** 64, mas não para 3 ** 65? Bem, eu não sei. Provavelmente, isso está de alguma forma relacionado à limitação do número de graus calculados anteriormente pelo sistema na memória. Eu posso estar errado. O próximo passo que pretendo dar é aprofundar o código-fonte Python no meu tempo livre e tentar entender o que está acontecendo. Ainda estou procurando uma resposta para minha pergunta. Portanto, se você tiver alguma idéia, compartilhe-a nos comentários.
Quero que você se inspire neste post para encontrar uma solução para seus problemas. Você nunca sabe onde as respostas o levarão. Por fim, você pode aprender algo completamente novo, como aconteceu comigo. Espero que a chama da curiosidade ainda esteja queimando em você!
Você já reparou coisas semelhantes? Aguardando seus comentários!