A extração de funcionalidades em métodos ou funções é essencial para modularidade, legibilidade e interoperabilidade do código, especialmente em OOP.
Mas isso significa que mais chamadas de funções serão feitas.
Como a divisão de nosso código em métodos ou funções realmente afeta o desempenho em linguagens modernas * ?
* Os mais populares: C, Java, C ++, C #, Python, JavaScript, Ruby ...
Respostas:
Talvez. O compilador pode decidir "ei, essa função é chamada apenas algumas vezes, e eu devo otimizar a velocidade, por isso vou incorporar essa função". Essencialmente, o compilador substituirá a chamada de função pelo corpo da função. Por exemplo, o código fonte ficaria assim.
O compilador decide incorporar
DoSomethingElse
e o código se tornaQuando as funções não estão embutidas, sim, há um impacto no desempenho para fazer uma chamada de função. No entanto, é um sucesso tão minúsculo que apenas um código de desempenho extremamente alto se preocupa com chamadas de função. E nesses tipos de projetos, o código geralmente é escrito em assembly.
As chamadas de função (dependendo da plataforma) geralmente envolvem algumas 10s de instruções, incluindo salvar / restaurar a pilha. Algumas chamadas de função consistem em uma instrução de salto e retorno.
Mas há outras coisas que podem afetar o desempenho das chamadas de função. A função que está sendo chamada pode não ser carregada no cache do processador, causando uma falha no cache e forçando o controlador de memória a capturar a função da RAM principal. Isso pode causar um grande impacto no desempenho.
Em poucas palavras: as chamadas de função podem ou não afetar o desempenho. A única maneira de saber é criar um perfil do seu código. Não tente adivinhar onde estão os pontos lentos do código, porque o compilador e o hardware têm alguns truques incríveis nas mangas. Perfile o código para obter a localização dos pontos lentos.
fonte
É uma questão de implementação do compilador ou tempo de execução (e suas opções) e não pode ser dito com certeza.
Dentro de C e C ++, alguns compiladores alinham chamadas com base nas configurações de otimização - isso pode ser visto trivialmente examinando o assembly gerado ao examinar ferramentas como https://gcc.godbolt.org/
Outras linguagens, como Java, têm isso como parte do tempo de execução. Isso faz parte do JIT e foi elaborado nesta questão do SO . Em particular, veja as opções de JVM para HotSpot
Então, sim, o compilador HotSpot JIT incorporará métodos que atendem a determinados critérios.
O impacto disso é difícil de determinar, pois cada JVM (ou compilador) pode fazer as coisas de maneira diferente e tentar responder com o amplo toque de um idioma é quase certo de estar errado. O impacto só pode ser determinado adequadamente, criando um perfil do código no ambiente de execução apropriado e examinando a saída compilada.
Isso pode ser visto como uma abordagem equivocada, com o CPython não embutido, mas o Jython (Python executando na JVM) com algumas chamadas inline. Da mesma forma, o MRI Ruby não está embutido enquanto o JRuby o faria, e o ruby2c, que é um transpilador para ruby no C ... que pode estar embutido ou não, dependendo das opções do compilador C que foram compiladas.
Os idiomas não estão alinhados. Implementações podem .
fonte
Você está procurando desempenho no lugar errado. O problema com as chamadas de função não é que elas custam muito. Há outro problema. As chamadas de função podem ser totalmente gratuitas e você ainda tem esse outro problema.
É que uma função é como um cartão de crédito. Como você pode usá-lo facilmente, você tende a usá-lo mais do que deveria. Suponha que você chame 20% a mais do que precisa. Em seguida, o software grande típico contém várias camadas, cada chamada funciona na camada abaixo, de modo que o fator 1,2 pode ser composto pelo número de camadas. (Por exemplo, se houver cinco camadas e cada camada tiver um fator de desaceleração de 1,2, o fator de desaceleração composto é de 1,2 ^ 5 ou 2,5.) Essa é apenas uma maneira de pensar sobre isso.
Isso não significa que você deve evitar chamadas de função. O que isso significa é que, quando o código estiver em funcionamento, você deve saber como encontrar e eliminar o desperdício. Há muitos conselhos excelentes sobre como fazer isso em sites de stackexchange. Isso dá uma das minhas contribuições.
ADICIONADO: Pequeno exemplo. Certa vez, trabalhei em uma equipe de software no chão de fábrica que acompanhava uma série de ordens de serviço ou "trabalhos". Havia uma função
JobDone(idJob)
que poderia dizer se um trabalho foi feito. Um trabalho foi concluído quando todas as suas subtarefas foram concluídas e cada uma delas foi concluída quando todas as suas suboperações foram concluídas. Todas essas coisas foram mantidas em um banco de dados relacional. Uma única chamada para outra função poderia extrair todas essas informações, aJobDone
chamada outra função, ver se o trabalho estava concluído e jogar o resto fora. Então as pessoas poderiam escrever código facilmente como este:ou
Veja o ponto? A função era tão "poderosa" e fácil de chamar que foi chamada demais. Portanto, o problema de desempenho não era as instruções entrando e saindo da função. Era necessário que houvesse uma maneira mais direta de saber se os trabalhos foram feitos. Novamente, esse código poderia ter sido incorporado em milhares de linhas de código inocente. Tentar consertar isso com antecedência é o que todo mundo tenta fazer, mas é como tentar jogar dardos em um quarto escuro. O que você precisa é em vez de fazê-lo funcionar, e , em seguida, deixar que o "código lento" dizer-lhe o que é, simplesmente tomando tempo. Para isso, uso uma pausa aleatória .
fonte
Eu acho que realmente depende da linguagem e da função. Embora os compiladores c e c ++ possam incorporar muitas funções, esse não é o caso para Python ou Java.
Embora eu não conheça os detalhes específicos do java (exceto que todo método é virtual, mas sugiro que você verifique melhor a documentação), no Python tenho certeza de que não há inlining, nenhuma otimização da recursão da cauda e chamadas de função são muito caras.
As funções Python são basicamente objetos executáveis (e, na verdade, você também pode definir o método call () para transformar uma instância de objeto em uma função). Isso significa que há muita sobrecarga em chamá-los ...
MAS
quando você define variáveis dentro de funções, o intérprete usa o LOADFAST em vez da instrução LOAD normal no bytecode, tornando seu código mais rápido ...
Outra coisa é que, quando você define um objeto que pode ser chamado, padrões como memorização são possíveis e podem efetivamente acelerar muito o seu cálculo (com o custo de usar mais memória). Basicamente, é sempre uma troca. O custo das chamadas de função também depende dos parâmetros, porque eles determinam a quantidade de coisas que você realmente precisa copiar na pilha (portanto, em c / c ++, é prática comum passar grandes parâmetros como estruturas por ponteiros / referência em vez de por valor).
Eu acho que sua pergunta é, na prática, muito ampla para ser respondida completamente no stackexchange.
O que eu sugiro que você faça é começar com um idioma e estudar a documentação avançada para entender como as chamadas de função são implementadas por esse idioma específico.
Você ficará surpreso com quantas coisas aprenderá neste processo.
Se você tiver um problema específico, faça medições / criação de perfil e decida o clima, é melhor criar uma função ou copiar / colar o código equivalente.
se você fizer uma pergunta mais específica, seria mais fácil obter uma resposta mais específica, eu acho.
fonte
Avaliei a sobrecarga das chamadas de função C ++ diretas e virtuais no Xenon PowerPC há algum tempo .
As funções em questão tinham um único parâmetro e um único retorno; portanto, a passagem de parâmetros ocorreu nos registros.
Para encurtar a história, a sobrecarga de uma chamada de função direta (não virtual) foi de aproximadamente 5,5 nanossegundos ou 18 ciclos de relógio, em comparação com uma chamada de função embutida. A sobrecarga de uma chamada de função virtual foi de 13,2 nanossegundos, ou 42 ciclos de clock, em comparação com a linha.
Esses tempos provavelmente são diferentes em diferentes famílias de processadores. Meu código de teste está aqui ; você pode executar a mesma experiência no seu hardware. Use um timer de alta precisão como rdtsc para sua implementação do CFastTimer; a hora do sistema () não é suficientemente precisa.
fonte