Trabalhando com o princípio de responsabilidade única (SRP) no Python quando as chamadas são caras

12

Alguns pontos base:

  • As chamadas de método Python são "caras" devido à sua natureza interpretada . Em teoria, se o seu código for simples o suficiente, a quebra do código Python terá um impacto negativo além da legibilidade e da reutilização (o que é um grande ganho para os desenvolvedores, não para os usuários ).
  • O princípio de responsabilidade única (SRP) mantém o código legível, é mais fácil de testar e manter.
  • O projeto tem um tipo especial de experiência em que queremos código legível, testes e desempenho de tempo.

Por exemplo, código como este, que chama vários métodos (x4), é mais lento que o seguinte, que é apenas um.

from operator import add

class Vector:
    def __init__(self,list_of_3):
        self.coordinates = list_of_3

    def move(self,movement):
        self.coordinates = list( map(add, self.coordinates, movement))
        return self.coordinates

    def revert(self):
        self.coordinates = self.coordinates[::-1]
        return self.coordinates

    def get_coordinates(self):
        return self.coordinates

## Operation with one vector
vec3 = Vector([1,2,3])
vec3.move([1,1,1])
vec3.revert()
vec3.get_coordinates()

Em comparação com isso:

from operator import add

def move_and_revert_and_return(vector,movement):
    return list( map(add, vector, movement) )[::-1]

move_and_revert_and_return([1,2,3],[1,1,1])

Se eu devo paralelizar algo assim, é bastante objetivo que eu perco o desempenho. Mente que é apenas um exemplo; meu projeto possui várias mini-rotinas com matemática como essa - embora seja muito mais fácil trabalhar com, nossos criadores de perfil não gostam disso.


Como e onde adotamos o SRP sem comprometer o desempenho no Python, pois sua implementação inerente o afeta diretamente?

Existem soluções alternativas, como algum tipo de pré-processador que coloca as coisas em linha para o lançamento?

Ou o Python é simplesmente ruim em lidar com a quebra de código?

lucasgcb
fonte
4
Possível duplicata de A micro-otimização é importante ao codificar?
mosquito
19
Pelo que vale a pena, seus dois exemplos de código não diferem no número de responsabilidades. O SRP não é um método de contagem de exercícios.
Robert Harvey
2
@RobertHarvey Você está certo, desculpe pelo exemplo ruim e eu editarei um melhor quando tiver tempo. Em qualquer um dos casos, a legibilidade e a manutenção são prejudicadas e, eventualmente, o SRP se decompõe na base de código à medida que reduzimos as classes e seus métodos.
Lucasgcb 12/04/19
4
note que chamadas de função são caros em qualquer idioma , embora compiladores AOT tem o luxo de inlining
Eevee
6
Use uma implementação JITted de python, como PyPy. Deve principalmente corrigir esse problema.
Bakuriu 12/04/19

Respostas:

17

Python é simplesmente pobre em lidar com a quebra de código?

Infelizmente, sim, o Python é lento e há muitas histórias sobre pessoas que aumentam drasticamente o desempenho, incorporando funções e tornando seu código feio.

Existe uma solução alternativa, o Cython, que é uma versão compilada do Python e muito mais rápida.

--Edit Eu só queria abordar alguns dos comentários e outras respostas. Embora o impulso deles não seja talvez específico para python. mas otimização mais geral.

  1. Não otimize até ter um problema e procure gargalos

    Geralmente bons conselhos. Mas a suposição é que o código 'normal' geralmente é de desempenho. Nem sempre é esse o caso. Linguagens e estruturas individuais têm suas próprias idiossincrasias. Nesse caso, a função chama.

  2. São apenas alguns milissegundos, outras coisas serão mais lentas

    Se você estiver executando seu código em um computador desktop poderoso, provavelmente não se importa, desde que seu código de usuário único seja executado em alguns segundos.

    Mas o código comercial tende a ser executado para vários usuários e requer mais de uma máquina para suportar a carga. Se seu código for executado duas vezes mais rápido, significa que você pode ter o dobro do número de usuários ou metade do número de máquinas.

    Se você possui suas máquinas e data center, geralmente possui uma grande parcela de sobrecarga na energia da CPU. Se o seu código ficar um pouco lento, você poderá absorvê-lo, pelo menos até precisar comprar uma segunda máquina.

    Nestes dias de computação em nuvem, em que você usa apenas exatamente a energia de computação necessária e não mais, há um custo direto para o código sem desempenho.

    Melhorar o desempenho pode reduzir drasticamente as principais despesas de um negócio baseado em nuvem e o desempenho realmente deve estar na frente e no centro.

Ewan
fonte
1
Embora a resposta de Robert ajude a cobrir algumas bases para possíveis mal-entendidos por trás desse tipo de otimização (que se encaixa nessa pergunta ), acho que isso responde à situação um pouco mais diretamente e alinhada com o contexto do Python.
Lucasgcb 12/04/19
2
desculpe, é um pouco curto. Não tenho tempo para escrever mais. Mas acho que Robert está errado nessa questão. O melhor conselho com python parece ser o perfil conforme você codifica. Não supor que será performance e só otimizar se você encontrar um problema
Ewan
2
@ Ewan: Você não precisa escrever o programa inteiro primeiro para seguir meus conselhos. Um método ou dois é mais que suficiente para obter um perfil adequado.
Robert Harvey
1
você também pode tentar PyPy, que é um python JITted
Eevee
2
@ Ewan Se você está realmente preocupado com a sobrecarga de desempenho das chamadas de função, o que você está fazendo provavelmente não é adequado para python. Mas então eu realmente não consigo pensar em muitos exemplos lá. A grande maioria do código comercial é de E / S limitada e o material pesado da CPU geralmente é tratado chamando bibliotecas nativas (numpy, tensorflow e assim por diante).
Voo
50

Muitas preocupações potenciais de desempenho não são realmente um problema na prática. O problema que você levanta pode ser um deles. No vernáculo, chamamos a preocupação com esses problemas sem provar que são problemas reais de otimização prematura.

Se você estiver escrevendo um front-end para um serviço da Web, seu desempenho não será significativamente afetado pelas chamadas de função, porque o custo do envio de dados por uma rede excede em muito o tempo necessário para efetuar uma chamada de método.

Se você estiver escrevendo um loop apertado que atualiza uma tela de vídeo sessenta vezes por segundo, isso pode ser importante. Mas nesse ponto, afirmo que você tem problemas maiores se estiver tentando usar o Python para fazer isso, um trabalho para o qual o Python provavelmente não é adequado.

Como sempre, o jeito que você descobre é medir. Execute um gerador de perfil de desempenho ou alguns timers sobre seu código. Veja se é um problema real na prática.


O princípio da responsabilidade única não é uma lei ou mandato; é uma diretriz ou princípio. O design de software é sempre sobre compensações; não há absolutos. Não é incomum trocar a legibilidade e / ou a capacidade de manutenção por velocidade; portanto, você pode ter que sacrificar o SRP no altar do desempenho. Mas não faça essa troca a menos que saiba que tem um problema de desempenho.

Robert Harvey
fonte
3
Eu acho que isso era verdade, até que inventamos a computação em nuvem. Agora, uma das duas funções custa efetivamente 4 vezes mais do que o outro
Ewan
2
O @Ewan 4 vezes pode não importar até que você o avalie como sendo significativo o suficiente para se preocupar. Se Foo leva 1 ms e Bar leva 4 ms, isso não é bom. Até você perceber que a transmissão dos dados pela rede leva 200 ms. Nesse ponto, Bar sendo mais lento não importa tanto. (Apenas um exemplo possível de onde ser X vezes mais lento não faz uma diferença notável ou impactante, não pretende ser necessariamente super-realista.)
Becuzz
8
@Ewan Se a redução na conta economizar US $ 15 / mês, mas levar um contratado de US $ 125 / hora por 4 horas para corrigi-lo e testá-lo, eu poderia facilmente justificar que não vale a pena o tempo de uma empresa (ou pelo menos não faz o certo) agora, se o tempo de colocação no mercado é crucial, etc.). Sempre há trocas. E o que faz sentido em uma circunstância pode não em outra.
Becuzz
3
suas contas da AWS são realmente muito baixo
Ewan
6
O @Ewan AWS arredonda o teto em lotes de qualquer maneira (o padrão é 100ms). O que significa que esse tipo de otimização só poupa qualquer coisa se evita consistentemente empurrá-lo para a próxima parte.
Delioth 12/04/19
2

Primeiro, alguns esclarecimentos: Python é uma linguagem. Existem vários intérpretes diferentes que podem executar código escrito na linguagem Python. A implementação de referência (CPython) geralmente é o que está sendo referenciado quando alguém fala sobre "Python" como se fosse uma implementação, mas é importante ser preciso ao falar sobre características de desempenho, pois elas podem diferir bastante entre implementações.

Como e onde adotamos o SRP sem comprometer o desempenho no Python, pois sua implementação inerente o afeta diretamente?

Caso 1.) Se você possui um código Python puro (<= Python Language versão 3.5, 3.6 possui "suporte ao nível beta"), que depende apenas de módulos Python puros, você pode adotar o SRP em qualquer lugar e usar o PyPy para executá-lo. PyPy ( https://morepypy.blogspot.com/2019/03/pypy-v71-released-now-uses-utf-8.html ) é um interpretador Python que possui um Just in Time Compiler (JIT) e pode remover a função sobrecarga de chamada, desde que tenha tempo suficiente para "aquecer" rastreando o código executado (alguns segundos IIRC). **

Se você estiver restrito a usar o interpretador CPython, poderá extrair as funções lentas em extensões escritas em C, que serão pré-compiladas e não sofrerão nenhuma sobrecarga do interpretador. Você ainda pode usar o SRP em qualquer lugar, mas seu código será dividido entre Python e C. Se isso é melhor ou pior para a manutenção do que abandonar seletivamente o SRP, mas aderir apenas ao código Python depende da sua equipe, mas se você tiver seções críticas de desempenho do seu sem dúvida, será mais rápido do que o código Python puro mais otimizado, interpretado pelo CPython. Muitas das bibliotecas matemáticas mais rápidas do Python usam esse método (IIRC numpy e scipy). Qual é uma boa sequência para o caso 2 ...

Caso 2.) Se você possui código Python que usa extensões C (ou depende de bibliotecas que usam extensões C), o PyPy pode ou não ser útil, dependendo de como foram escritas. Consulte http://doc.pypy.org/en/latest/extending.html para obter detalhes, mas o resumo é que o CFFI possui uma sobrecarga mínima enquanto o CTypes é mais lento (o uso com o PyPy pode ser ainda mais lento que o CPython)

Cython ( https://cython.org/ ) é outra opção com a qual não tenho tanta experiência. Eu o menciono por uma questão de exaustividade, para que minha resposta possa "se sustentar por si mesma", mas não reivindique nenhum conhecimento. Pelo meu uso limitado, parecia que eu tinha que trabalhar mais para obter as mesmas melhorias de velocidade que eu poderia obter "de graça" com o PyPy, e se eu precisasse de algo melhor que o PyPy, seria igualmente fácil escrever minha própria extensão C ( que tem o benefício se eu reutilizar o código em outro local ou extrair parte dele em uma biblioteca, todo o meu código ainda poderá ser executado em qualquer interpretador Python e não é necessário que o Cython seja executado).

Estou com medo de estar "bloqueado" no Cython, enquanto qualquer código escrito para o PyPy também pode ser executado no CPython.

** Algumas notas extras sobre PyPy em produção

Tenha muito cuidado ao fazer escolhas que tenham o efeito prático de "prender você" ao PyPy em uma grande base de código. Como algumas bibliotecas de terceiros (muito populares e úteis) não são boas pelas razões mencionadas anteriormente, isso pode causar decisões muito difíceis posteriormente, se você perceber que precisa de uma dessas bibliotecas. Minha experiência é principalmente no uso do PyPy para acelerar alguns (mas não todos) microsserviços que são sensíveis ao desempenho em um ambiente de empresa, onde adiciona complexidade insignificante ao nosso ambiente de produção (já temos vários idiomas implementados, alguns com diferentes versões principais, como 2,7 vs 3.5 rodando de qualquer maneira).

Descobri que o uso do PyPy e do CPython me obrigava a escrever código regularmente, que depende apenas de garantias feitas pela própria especificação da linguagem e não de detalhes de implementação sujeitos a alterações a qualquer momento. Você pode achar que esses detalhes são um fardo extra, mas achei valioso em meu desenvolvimento profissional e acho que é "saudável" para o ecossistema Python como um todo.

Steven Jackson
fonte
sim! Eu estive considerando o foco nas extensões C para este caso, em vez de abandonar o princípio e escrever código curinga, as outras respostas me deram a impressão de que seria lento independentemente, a menos que eu trocasse do intérprete de referência - Para esclarecer, o OOP ainda ser uma abordagem sensata na sua opinião?
Lucasgcb 13/04/19
1
com o caso 1 (2º parágrafo), você não recebe o mesmo overhead chamando as funções, mesmo que as funções sejam cumpridas?
Ewan
CPython é o único intérprete que geralmente é levado a sério. O PyPy é interessante , mas certamente não está vendo nenhum tipo de adoção generalizada. Além disso, seu comportamento difere do CPython e não funciona com alguns pacotes importantes, como o scipy. Poucos desenvolvedores sãos recomendariam o PyPy para produção. Como tal, a distinção entre a linguagem e a implementação é irrelevante na prática.
jpmc26
Eu acho que você acertou a unha na cabeça. Não há razão para você não ter um intérprete melhor ou um compilador. Não é intrínseco ao python como linguagem. Você está apenas preso com realidades práticas
Ewan
@ jpmc26 Eu usei o PyPy na produção e recomendo considerar isso com outros desenvolvedores experientes. É ótimo para microsserviços usando falconframework.org para APIs de descanso leve (como um exemplo). Comportamento diferente, porque os desenvolvedores dependem de detalhes de implementação que NÃO são garantia da linguagem, não é um motivo para não usar o PyPy. É um motivo para reescrever seu código. O mesmo código pode quebrar de qualquer maneira se o CPython fizer alterações em sua implementação (o que é gratuito, desde que ainda esteja em conformidade com as especificações da linguagem).
9139 Steven Jackson