Blog Bugginho Academy

Por que o Python é tão lento?

Python está crescendo em popularidade. É usado no DevOps, Data Science, Desenvolvimento Web e Segurança.

No entanto, não ganha medalhas por velocidade.

Como comparar Java em termos de velocidade com C, C++, C# ou Python? A resposta depende muito do tipo de aplicativo que você está executando. Nenhum benchmark é perfeito, mas o The Computer Language Benchmarks Game é um bom ponto de partida .

Eu tenho me referido ao Computer Language Benchmarks Game por mais de uma década. Em comparação com outras linguagens como Java, C #, Go, JavaScript, C ++, o Python é um dos mais lentos . Isso inclui compiladores JIT (C#, Java) e AOT (C, C ++), bem como linguagens interpretadas como JavaScript.

OBS: Quando digo “Python”, estou falando sobre a implementação de referência da linguagem, CPython. Vou me referir a outros tempos de execução neste artigo.

Eu quero responder a esta pergunta: Quando o Python completa um aplicativo comparável de 2 a 10x mais lento que outro idioma, por que é lento e não podemos torná-lo mais rápido?

Aqui estão as principais teorias:

  • “É o GIL (Global Interpreter Lock)”
  • “É porque é interpretado e não compilado”
  • “É porque é uma linguagem dinamicamente tipada”

Qual dessas razões tem o maior impacto no desempenho?

“É o GIL”

Computadores modernos vêm com CPUs que possuem múltiplos núcleos e, às vezes, múltiplos processadores. Para utilizar todo esse poder extra de processamento, o Sistema Operacional define uma estrutura de baixo nível chamada thread, onde um processo (por exemplo, Chrome Browser) pode gerar vários threads e ter instruções para o sistema interno. Dessa forma, se um processo é particularmente intensivo da CPU, essa carga pode ser compartilhada entre os núcleos e isso faz com que a maioria das tarefas conclua tarefas mais rapidamente.

Meu navegador Chrome, enquanto escrevo este artigo, tem 44 segmentos abertos. Lembre-se de que a estrutura e a API de segmentação são diferentes entre os sistemas baseados em POSIX (por exemplo, Mac OS e Linux) e Windows OS. O sistema operacional também lida com o agendamento de threads.

Se você não fez programação multi-threaded antes, um conceito que você precisa para se familiarizar rapidamente é Lock. Ao contrário de um processo de encadeamento único, é necessário garantir que, ao alterar variáveis ​​na memória, vários encadeamentos não tentem acessar / alterar o mesmo endereço de memória ao mesmo tempo.

Quando o CPython cria variáveis, ele aloca a memória e depois conta quantas referências a essa variável existem, esse é um conceito conhecido como contagem de referência. Se o número de referências for 0, ele libera essa parte da memória do sistema. É por isso que a criação de uma variável “temporária”, digamos, no escopo de um loop for, não explode o consumo de memória de seu aplicativo.

O desafio torna-se então quando as variáveis ​​são compartilhadas em vários segmentos, como o CPython bloqueia a contagem de referência. Existe um “Global Interpreter Lock” que controla cuidadosamente a execução de threads. O intérprete só pode executar uma operação por vez, independentemente de quantos segmentos ela tenha.

O que isso significa para o desempenho do aplicativo Python?

Se você tiver um aplicativo de um único interpretador de encadeamento único. Não fará diferença para a velocidade . Removendo o GIL não teria impacto sobre o desempenho do seu código.

Se você quisesse implementar a simultaneidade dentro de um único interpretador (processo Python) usando threading com IO intensivos (eg Network IO ou Disk IO), você veria as conseqüências da contenção de GIL.

 

De: David Beazley GIL, disponível em http://dabeaz.blogspot.com/2010/01/python-gil-visualized.html

 

Se você tem um aplicativo da web (por exemplo, Django) e está usando o WSGI, então cada solicitação para seu aplicativo da Web é um interpretador Python separado, portanto, há apenas um lock por solicitação. Como o interpretador Python demora a ser iniciado, algumas implementações do WSGI têm um “Daemon Mode” que mantém o(s) processo(s) do Python em movimento para você.

E quanto às outras Python runtimes?

O PyPy tem um GIL e é normalmente 3x mais rápido que o CPython.

O Jython não possui um GIL porque um encadeamento do Python no Jython é representado por um encadeamento Java e se beneficia do sistema de gerenciamento de memória da JVM.

Como o JavaScript faz isso?

Bem, em primeiro lugar, todos os mecanismos de JavaScript usam Mark-and-sweep e Garbage Collection. Como afirmado, a principal necessidade do GIL é o algoritmo de gerenciamento de memória do CPython.

O JavaScript não tem um GIL, mas também é single-threaded, portanto não requer um. O loop de eventos e o padrão Promise / Callback do JavaScript são como a programação assíncrona é obtida no lugar da simultaneidade. Python tem uma coisa parecida com o loop de eventos assíncrono.

“É porque é uma linguagem interpretada”

Eu ouço muito isso e acho uma simplificação grosseira da maneira como o CPython realmente funciona. Se em um terminal você escrevesse python myscript.py, o CPython iniciaria uma longa seqüência de leitura, lexação, análise, compilação, interpretação e execução desse código.

Se você estiver interessado em saber como esse processo funciona, escrevi sobre isso antes:

https://hackernoon.com/modifying-the-python-language-in-7-minutes-b94b0a99ce14

Um ponto importante nesse processo é a criação de um arquivo .pyc, no estágio de compilador, a sequência de bytecode é gravada em um arquivo dentro de __pycache__/ no Python 3 ou no mesmo diretório no Python 2. Isso não se aplica apenas a seu script, mas todo o código que você importou, incluindo módulos de terceiros.

Então, na maior parte do tempo (a menos que você escreva um código que você só executa uma vez?), O Python está interpretando o bytecode e executando-o localmente. Compare isso com Java e C# .NET:

Java compila para uma “Linguagem Intermediária” e a Máquina Virtual Java lê o bytecode e o just-in-time compila-o para código de máquina. O .NET CIL é o mesmo, o .NET Common-Language-Runtime, CLR, utiliza a compilação just-in-time para código de máquina.

Então, por que o Python é muito mais lento que Java e C# nos benchmarks se todos eles usam uma máquina virtual e algum tipo de Bytecode? Em primeiro lugar, .NET e Java são JIT-Compiled.

A compilação JIT ou just-in-time requer um idioma intermediário para permitir que o código seja dividido em partes (ou frames). Compiladores Ahead of Time (AOT) são projetados para garantir que a CPU possa entender todas as linhas do código antes que qualquer interação ocorra.

O JIT em si não torna a execução mais rápida, porque ainda está executando as mesmas sequências de bytecode. No entanto, o JIT permite que otimizações sejam feitas em tempo de execução.

Um bom otimizador JIT vai ver quais partes do aplicativo estão sendo executadas, chamam esses “hot spots”. Em seguida, ele fará otimizações para esses bits de código, substituindo-os por versões mais eficientes.

Isso significa que quando seu aplicativo faz a mesma coisa várias vezes, pode ser significativamente mais rápido. Além disso, lembre-se de que Java e C# são linguagens fortemente tipadas, portanto, o otimizador pode fazer muito mais suposições sobre o código.

O PyPy tem um JIT e, como mencionado na seção anterior, é significativamente mais rápido que o CPython. Este artigo de benchmark de desempenho entra em mais detalhes:

https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b

Então, por que o CPython não usa um JIT?

Existem desvantagens para os JITs: um deles é o tempo de inicialização. O tempo de inicialização do CPython já é comparativamente lento, o PyPy é 2 a 3 vezes mais lento para iniciar do que o CPython. A Java Virtual Machine é notoriamente lenta para inicializar. O .NET CLR contorna isso iniciando na inicialização do sistema, mas os desenvolvedores do CLR também desenvolvem o sistema operacional no qual o CLR é executado.

Se você tem um único processo Python em execução por um longo tempo, com código que pode ser otimizado porque contém “hot spots”, então um JIT faz muito sentido.

No entanto, o CPython é uma implementação de propósito geral . Portanto, se você estivesse desenvolvendo aplicativos de linha de comando usando o Python, ter que esperar por um JIT para iniciar toda vez que o CLI fosse chamado seria terrivelmente lento.

CPython tem que tentar servir tantos casos de uso quanto possível. Havia a possibilidade de conectar um JIT ao CPython, mas este projeto foi paralisado.

Se você deseja os benefícios de um JIT e tiver uma carga de trabalho adequada, use o PyPy.

“É porque é uma linguagem dinamicamente tipada”

Em uma linguagem “Statically-Typed”, você precisa especificar o tipo de uma variável quando ela é declarada. Esses incluem C, C ++, Java, C#, Go.

Em uma linguagem tipada dinamicamente, ainda há o conceito de tipos, mas o tipo de variável é dinâmico.

Neste exemplo, Python cria uma segunda variável com o mesmo nome e um tipo de str e desaloca a memória criada para a primeira instância de a

Linguagens com tipagem estática não são projetadas para tornar sua vida difícil, elas são projetadas dessa maneira por causa da maneira como o processador opera. Se tudo eventualmente precisar se igualar a uma simples operação binária, você terá que converter objetos e tipos em uma estrutura de dados de baixo nível.

O Python faz isso por você, você nunca vê, nem precisa se importar.

Não ter que declarar o tipo não é o que torna o Python lento, o design da linguagem Python permite que você faça quase qualquer coisa dinâmicamente. Você pode substituir os métodos em objetos em tempo de execução, você pode macular chamadas de sistema de baixo nível para um valor declarado em tempo de execução. Quase tudo é possível.

É esse design que torna incrivelmente difícil otimizar o Python.

Para ilustrar o meu ponto, vou usar uma ferramenta de rastreamento syscall que funciona no Mac OS chamado Dtrace. As distribuições CPython não vêm com o DTrace builtin, portanto, é necessário recompilar o CPython. Estou usando 3.6.6 para minha demonstração

Agora o python.exe terá rastreadores do Dtrace em todo o código. Paul Ross escreveu um incrível Lightning Talk no Dtrace . Você pode fazer o download de arquivos iniciais do DTrace para o Python para medir chamadas de funções, tempo de execução, tempo de CPU, syscalls, todos os tipos de diversão. por exemplo

O rastreador py_callflow mostra todas as chamadas de função em seu aplicativo

Então, a tipagem dinâmica do Python torna isso lento?

  • Comparar e converter tipos é dispendioso, toda vez que uma variável é lida, gravada ou referenciada, o tipo é verificado
  • É difícil otimizar uma linguagem tão dinâmica. A razão pela qual muitas alternativas ao Python são muito mais rápidas é que elas fazem concessões à flexibilidade em nome do desempenho.
  • Observar o Cython , que combina os tipos C-Static e Python para otimizar o código onde os tipos são conhecidos, pode fornecer uma melhoria no desempenho de 84x .

Conclusão

O Python é basicamente lento devido à sua natureza dinâmica e versatilidade. Ele pode ser usado como uma ferramenta para todos os tipos de problemas, onde alternativas mais otimizadas e mais rápidas provavelmente estão disponíveis.

No entanto, existem maneiras de otimizar seus aplicativos Python, aproveitando async, entendendo as ferramentas de criação de perfil e considerando o uso de vários interpretadores.

Para aplicativos em que o tempo de inicialização não é importante e o código beneficiaria um JIT, considere o PyPy.

Para partes do seu código em que o desempenho é crítico e você tem mais variáveis ​​tipificadas estatisticamente, considere o uso do Cython .

 

Esse texto é uma tradução na integra do texto “Why is Python so slow?” disponível no Link https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b

Todos os créditos da publicação são para Anthony Shaw.

Bugginho Developer

Comentar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Your Header Sidebar area is currently empty. Hurry up and add some widgets.