Eu sou um usuário de Python de longa data. Alguns anos atrás, comecei a aprender C ++ para ver o que ele poderia oferecer em termos de velocidade. Durante esse período, eu continuaria usando o Python como uma ferramenta para prototipagem. Parecia que este era um bom sistema: desenvolvimento ágil com Python, execução rápida em C ++.
Recentemente, tenho usado o Python cada vez mais, e aprendendo a evitar todas as armadilhas e antipadrões que rapidamente usei nos meus anos anteriores com a linguagem. Entendo que o uso de certos recursos (lista de compreensões, enumerações etc.) pode aumentar o desempenho.
Mas existem limitações técnicas ou recursos de linguagem que impedem que meu script Python seja tão rápido quanto um programa C ++ equivalente?
fonte
Respostas:
Eu meio que bati nessa parede quando aceitei um trabalho de programação em Python em tempo integral há alguns anos. Eu amo Python, realmente, mas quando comecei a fazer alguns ajustes de desempenho, tive alguns choques rudes.
Os Pythonistas rigorosos podem me corrigir, mas aqui estão as coisas que encontrei, pintadas em traços muito amplos.
Isso tem um impacto no desempenho, porque significa que há níveis extras de indireção no tempo de execução, além de consumir enormes quantidades de memória em comparação com outros idiomas.
Outros podem conversar com o modelo de execução, mas o Python é um compilador em tempo de execução e, em seguida, é interpretado, o que significa que ele não percorre todo o caminho do código da máquina. Isso também tem um impacto no desempenho. Você pode facilmente vincular módulos C ou C ++ ou encontrá-los, mas se você executar o Python diretamente, terá um impacto no desempenho.
Agora, nos benchmarks de serviços da Web, o Python se compara favoravelmente com outras linguagens de compilação em tempo de execução, como Ruby ou PHP. Mas está bem atrás da maioria das linguagens compiladas. Até as linguagens que compilam na linguagem intermediária e executam em uma VM (como Java ou C #) fazem muito, muito melhor.
Aqui está um conjunto realmente interessante de testes de benchmark aos quais me refiro ocasionalmente:
http://www.techempower.com/benchmarks/
(Tudo o que foi dito, eu ainda amo muito o Python, e se tiver a chance de escolher o idioma em que estou trabalhando, é a minha primeira escolha. Na maioria das vezes, de qualquer forma, não sou limitado por requisitos de taxa de transferência loucos.)
fonte
__slots__
. PyPy deve se sair muito melhor nesse aspecto, mas não sei o suficiente para julgar.A implementação de referência do Python é o intérprete “CPython”. Ele tenta ser razoavelmente rápido, mas atualmente não emprega otimizações avançadas. E para muitos cenários de uso, isso é uma coisa boa: a compilação para algum código intermediário ocorre imediatamente antes do tempo de execução, e toda vez que o programa é executado, o código é compilado novamente. Portanto, o tempo necessário para a otimização deve ser ponderado em relação ao tempo ganho pelas otimizações - se não houver um ganho líquido, a otimização será inútil. Para um programa de execução muito longa, ou um programa com loops muito apertados, empregar otimizações avançadas seria útil. No entanto, o CPython é usado para alguns trabalhos que impedem a otimização agressiva:
Scripts de execução curta, usados, por exemplo, para tarefas sysadmin. Muitos sistemas operacionais como o Ubuntu constroem boa parte de sua infraestrutura em cima do Python: o CPython é rápido o suficiente para o trabalho, mas praticamente não tem tempo de inicialização. Contanto que seja mais rápido que o bash, é bom.
O CPython deve ter semântica clara, pois é uma implementação de referência. Isso permite otimizações simples como "otimizar a implementação do operador foo" ou "compilar as compreensões da lista para um bytecode mais rápido", mas geralmente impedirá otimizações que destroem informações, como funções embutidas.
Obviamente, existem mais implementações em Python do que apenas o CPython:
O Jython é construído sobre a JVM. A JVM pode interpretar ou compilar JIT o bytecode fornecido e possui otimizações guiadas por perfil. Ele sofre com um alto tempo de inicialização e leva um tempo até o JIT entrar em ação.
PyPy é um estado da arte, JITting Python VM. PyPy é escrito em RPython, um subconjunto restrito de Python. Esse subconjunto remove alguma expressividade do Python, mas permite que o tipo de qualquer variável seja inferido estaticamente. A VM gravada no RPython pode ser transpilada para C, o que fornece desempenho semelhante ao RPython C. No entanto, o RPython ainda é mais expressivo que o C, o que permite o desenvolvimento mais rápido de novas otimizações. PyPy é um exemplo de inicialização do compilador. O PyPy (não o RPython!) É compatível principalmente com a implementação de referência do CPython.
Cython é (como RPython) um dialeto incompatível com digitação estática. Ele também transpila para o código C e é capaz de gerar facilmente extensões C para o interpretador CPython.
Se você estiver disposto a traduzir seu código Python para Cython ou RPython, terá um desempenho semelhante ao C. No entanto, eles não devem ser entendidos como "um subconjunto do Python", mas como "C com sintaxe Pythonic". Se você mudar para o PyPy, seu código Python baunilha receberá um considerável aumento de velocidade, mas também não poderá interagir com extensões escritas em C ou C ++.
Mas quais propriedades ou recursos impedem o Python de baunilha de atingir níveis de desempenho do tipo C, além dos longos tempos de inicialização?
Contribuintes e financiamento. Ao contrário de Java ou C #, não há uma única empresa motriz por trás do idioma com interesse em fazer desse idioma o melhor de sua classe. Isso restringe o desenvolvimento principalmente a voluntários e concessões ocasionais.
Ligação tardia e falta de digitação estática. Python nos permite escrever porcaria assim:
No Python, qualquer variável pode ser reatribuída a qualquer momento. Isso evita o armazenamento em cache ou inlining; qualquer acesso precisa passar pela variável. Esse indireção diminui o desempenho. Obviamente: se o seu código não faz coisas tão insanas para que cada variável possa receber um tipo definitivo antes da compilação e cada variável seja atribuída apenas uma vez, então - em teoria - um modelo de execução mais eficiente pode ser escolhido. Um idioma com isso em mente forneceria uma maneira de marcar identificadores como constantes e, pelo menos, permitir anotações de tipo opcionais ("digitação gradual").
Um modelo de objeto questionável. A menos que os slots sejam usados, é difícil descobrir quais campos um objeto possui (um objeto Python é essencialmente uma tabela de campos de hash). E mesmo quando estamos lá, ainda não temos idéia de quais tipos esses campos têm. Isso evita a representação de objetos como estruturas compactadas, como é o caso do C ++. (É claro que a representação de objetos em C ++ também não é ideal: devido à natureza estrutural, até os campos privados pertencem à interface pública de um objeto.)
Coleta de lixo. Em muitos casos, o GC poderia ser completamente evitado. C ++ nos permite alocar estaticamente objetos que são destruídos automaticamente quando o escopo atual é deixado:
Type instance(args);
. Até então, o objeto está vivo e pode ser emprestado para outras funções. Isso geralmente é feito via "passagem por referência". Idiomas como Rust permitem que o compilador verifique estaticamente se nenhum ponteiro para esse objeto excede a vida útil do objeto. Esse esquema de gerenciamento de memória é totalmente previsível, altamente eficiente e atende à maioria dos casos sem gráficos complicados de objetos. Infelizmente, o Python não foi projetado com o gerenciamento de memória em mente. Em teoria, a análise de escape pode ser usada para encontrar casos em que o GC pode ser evitado. Na prática, cadeias simples de métodos comofoo().bar().baz()
terá que alocar um grande número de objetos de vida curta no heap (o GC geracional é uma maneira de manter esse problema pequeno).Em outros casos, o programador já pode saber o tamanho final de algum objeto, como uma lista. Infelizmente, o Python não oferece uma maneira de comunicar isso ao criar uma nova lista. Em vez disso, novos itens serão enviados para o final, o que pode exigir várias realocações. Algumas notas:
Listas de um tamanho específico podem ser criadas como
fixed_size = [None] * size
. No entanto, a memória dos objetos nessa lista precisará ser alocada separadamente. Contraste C ++, onde podemos fazerstd::array<Type, size> fixed_size
.Matrizes compactadas de um tipo nativo específico podem ser criadas em Python através do
array
módulo interno. Além disso,numpy
oferece representações eficientes de buffers de dados com formas específicas para tipos numéricos nativos.Sumário
O Python foi projetado para facilitar o uso, não para o desempenho. Seu design dificulta a criação de uma implementação altamente eficiente. Se o programador se abster de recursos problemáticos, um compilador que entenda os idiomas restantes poderá emitir código eficiente que pode rivalizar com C no desempenho.
fonte
Sim. O principal problema é que a linguagem é definida como dinâmica - ou seja, você nunca sabe o que está fazendo até estar prestes a fazê-lo. Isso faz com que seja muito difícil de produzir código máquina eficiente, porque você não sabe o que o código de máquina produtos para . Os compiladores JIT podem fazer algum trabalho nessa área, mas nunca é comparável ao C ++, porque o compilador JIT simplesmente não pode gastar tempo e memória executando, pois esse é o tempo e a memória que você não está gastando executando seu programa, e há limites rígidos sobre o que eles podem alcançar sem quebrar a semântica da linguagem dinâmica.
Não vou afirmar que essa é uma troca inaceitável. Mas é fundamental para a natureza do Python que implementações reais nunca sejam tão rápidas quanto as implementações em C ++.
fonte
Existem três fatores principais que afetam o desempenho de todas as linguagens dinâmicas, algumas mais que outras.
Para C / C ++, os custos relativos desses três fatores são quase zero. As instruções são executadas diretamente pelo processador, o despacho leva no máximo um ou dois indícios, a memória heap nunca é alocada, a menos que você o diga. Código bem escrito pode se aproximar da linguagem assembly.
Para C # / Java com compilação JIT, os dois primeiros são baixos, mas a memória coletada de lixo tem um custo. Um código bem escrito pode se aproximar de 2x C / C ++.
Para Python / Ruby / Perl, o custo de todos esses três fatores é relativamente alto. Pense 5x em comparação com C / C ++ ou pior. (*)
Lembre-se de que o código da biblioteca de tempo de execução pode muito bem ser escrito no mesmo idioma dos seus programas e ter as mesmas limitações de desempenho.
(*) À medida que a compilação Just-In_Time (JIT) é estendida a esses idiomas, eles também se aproximam (normalmente 2x) da velocidade do código C / C ++ bem escrito.
Deve-se notar também que, uma vez que a lacuna é estreita (entre idiomas concorrentes), as diferenças são dominadas por algoritmos e detalhes de implementação. O código JIT pode superar o C / C ++ e o C / C ++ pode superar a linguagem assembly, porque é apenas mais fácil escrever um bom código.
fonte
Hash
classe Rubinius (uma das principais estruturas de dados em Ruby) é escrita em Ruby e apresenta um desempenho comparável, às vezes até mais rápido, do que aHash
classe de YARV, escrita em C. E uma das razões é que grandes partes do tempo de execução de Rubinius sistema são escritas em ruby, de modo que eles podem ...Não. É apenas uma questão de dinheiro e recursos despejados para fazer o C ++ rodar rápido versus dinheiro e recursos despejados para fazer o Python rodar rápido.
Por exemplo, quando a Self VM foi lançada, não era apenas o idioma OO dinâmico mais rápido, era o período do idioma OO mais rápido. Apesar de ser uma linguagem incrivelmente dinâmica (muito mais que Python, Ruby, PHP ou JavaScript, por exemplo), foi mais rápida que a maioria das implementações de C ++ disponíveis.
Mas a Sun cancelou o projeto Self (uma linguagem OO de uso geral madura para o desenvolvimento de grandes sistemas) para se concentrar em uma pequena linguagem de script para menus animados em caixas de TV (você deve ter ouvido falar sobre isso, chama-se Java). mais financiamento. Ao mesmo tempo, Intel, IBM, Microsoft, Sun, Metrowerks, HP et al. gastou grandes quantias de dinheiro e recursos, tornando o C ++ rápido. Os fabricantes de CPU adicionaram recursos aos seus chips para acelerar o C ++. Os sistemas operacionais foram escritos ou modificados para acelerar o C ++. Então, o C ++ é rápido.
Não estou muito familiarizado com o Python, sou mais uma pessoa Ruby, por isso darei um exemplo do Ruby: a
Hash
classe (equivalente em função e importância aodict
Python) na implementação do Rubinius Ruby é escrita em 100% puro Ruby; no entanto, ele compete favoravelmente e às vezes supera aHash
classe no YARV, escrita em C. otimizado à mão. E comparado a alguns dos sistemas comerciais Lisp ou Smalltalk (ou a mencionada Self VM), o compilador de Rubinius não é tão inteligente .Não há nada inerente ao Python que o torne lento. Existem recursos nos processadores e sistemas operacionais de hoje que prejudicam o Python (por exemplo, a memória virtual é conhecida por ser terrível para o desempenho da coleta de lixo). Existem recursos que ajudam o C ++, mas não ajudam o Python (as CPUs modernas tentam evitar falhas de cache, porque são muito caras. Infelizmente, é difícil evitar falhas de cache quando você tem OO e polimorfismo. Em vez disso, você deve reduzir o custo do cache A CPU Azul Vega, projetada para Java, faz isso.)
Se você gasta tanto dinheiro, pesquisa e recursos para tornar o Python rápido, como foi feito para C ++, e gasta tanto dinheiro, pesquisa e recursos para tornar sistemas operacionais que fazem os programas em Python rodarem mais rápido quanto foi feito para C ++ e você gasta como muito dinheiro, pesquisa e recursos para criar CPUs que executam programas em Python mais rápido que o C ++, então não há dúvida de que o Python poderia atingir desempenho comparável ao C ++.
Vimos com o ECMAScript o que pode acontecer se apenas um jogador leva a sério o desempenho. Em um ano, tivemos basicamente um aumento de 10 vezes no desempenho geral para todos os principais fornecedores.
fonte