Qual o custo de chamar muitas funções? – c c++ funções

Pergunta:


Recentemente, diante de uma discussão sobre Clean Code e melhores práticas de programação, um colega de trabalho comentou que em seu emprego anterior teve muita resistência por parte dos demais programadores para começar a dividir o código e torná-lo mais legível.

O principal motivo da resistência sempre foi de que muitas chamadas de funções iriam empilhar muitos dados no stack e consequentemente perder muita performance.

Particularmente, não vejo como uma chamada de função pode gerar um impacto tão negativo, mas eu digo isso baseado em uma experiência principalmente de linguagens de alto nível como Java, C# e PHP. Considerando o uso de uma linguagem de baixo nível (onde eu posso de fato gerenciar a memória) como C ou C++:

Qual é o custo de chamar 10 funções a mais?

Como consigo calcular a quantidade de memória gasta nesta operação?

Detalhe: No meu entendimento, a diferença de performance é extremamente desprezível, e portanto estou meramente interessado em saber como calcular o custo das chamadas.

Autor da pergunta jlHertel

Jefferson Quesado

Performance

O custo de chamar função é pequeno ou grande dependendo de como você olha. Em gera o custo é de salvar alguns registradores em memória (muito provavelmente ficará no cache L1 que é muito rápido, e e depois recuperar o estado original dos registradores.

Além disto pode haver cópias de dados por causa da passagem de parâmetro. Se isso não ocorreria se não fosse uma função depende.

Em certas circunstâncias o compilador consegue linearizar a função, ou seja, em vez de chamá-la (call) ele copia o código da função para o lugar que estava chamando. Você conseguirá a legibilidade e a performance.

Se o compilador não fizer isso é porque não pode (ok, há situações que o humano sabe que pode e o compilador não) ou não compensa fazer o inline da função.

Na pergunta linkada acima falo disto. Se a função não for minúscula e executar por um tempo muito curto não compensa. Também não compensa otimizar o que será chamado poucas vezes, mas isso nem sempre o compilador consegue saber.

O programador pode saber até que algo é chamado muitas vezes mas na verdade em um monte de lugar que há espera natural, então a performance não importa nada.

É verdade que em dispositivos com recursos reduzidos pode ser útil fazer uma pouco mais de otimização. A otimização pode ser muito pior nesses casos, porque se ganhar performance perde memória e pouca memória afeta muito a performance. E um dos recursos escassos é bateria. Código mais rápido costuma ser código mais econômico.

No passado, quando os computadores eram pouco potentes, isto era bem mais importante.

A performance é desprezível na maior parte dos casos e onde não for o compilador te ajuda. É claro que pode haver caso que a performance está ruim. Mas o mais provável é que o algoritmo é ruim e não a chamada da função que está atrapalhando.

Há casos em que a linearização da função pode causar custos extras e o que parecia uma ganho de performance certo, dá o contrário. E como a diferença é bem pequena, o programador “espertão” que fez o que ele achou que daria melhor performance acaba nem percebendo que ele fez o pior, ainda que esse pior também não vá atrapalhar nada.

Legibilidade

Eu jamais direi para sempre preferir reproduzir o código do que chamá-lo, mas raramente é preciso isto e só deve fazê-lo depois de verificar que a performance não é a esperada (tem que medir). O custo de otimizar pode não compensar o ganho. Porque a manutenção se torna mais difícil. Você quebra o DRY.

Claro que também entre o legível com menos performance e o legível com mais performance eu prefiro o de mais performance. Mas copiar o código para evitar a chamada dificilmente será mais legível que chamá-la.

Linguagens

Se a pessoa usa PHP, principalmente, ou Java ou C# ela não quer o máximo de performance. Essas linguagens podem ser rápidas, podem se beneficiar dessa otimização, mas não é tão comum assim precisar disto. Quando precisa de muita performance em geral o C ou C++ é preferido. Nesta linguagem faz mas sentido eliminar chamadas de função, mas depende da aplicação, depende do ponto da aplicação.

Memória

A pergunta fala em consumo de memória também. A função usará mais memória para salvar os registradores e é possível que também gaste um pouco mais por causa da cópia de parâmetros, mas nem sempre. Se o código foi feito direito isso pouco importa, porque a memória usada é do stack que já está alocada usando ou não.

Profilling

Você precisa de um profiler para verificar como o código está executando e indicar os custos. Valgrind é o mais conhecido para Linux e para Windows provavelmente deve estar usando Visual Studio que tem um profiler.

Tem outras maneiras, mas essa é a mais precisa e correta.

Diferenças de desempenho significativas por causa do tamanho da pilha de execução só fariam sentido em poucos casos. Os que consigo elencar que são realistas são esses:

  1. Uso intensivo de recursão a níveis bem profundos.

  2. Código que execute em dispositivos com pouquíssima memória disponível e baixíssimo poder computacional.

  3. Chamadas a funções dentro de loops que realizam computações intensas.

Há cenários que combinam mais do que um desses casos acima, mas a maior probabilidade é que você não esteja caindo em nenhum desses casos, e portanto ficar fazendo micro-otimizações de código sem ter uma base sólida de conhecimento calcada em evidências concretas acaba sendo burrice. Muitas vezes essas micro-otimizações acabam é surtindo o efeito oposto, ou seja, deixam o código mais lento.

Além disso, os compiladores de C++ são bem espertos, muuuito espertos. Os compiladores de C++ modernos foram desenvolvidos pela colaboração e trabalho árduo de milhares de pessoas extremamente competentes e inteligentes naquilo que faziam e eles são capazes de ver oportunidades e possibilidades de otimizações muito além do que a maioria dos programadores experientes em C++ e assembler seriam capazes. Há um monte de otimizações que se aplicam a esses casos e casos relacionados, tais como inlining, alocação de registradores para variáveis mais utilizadas, desenrolamento de laços, recursão de cauda, caches, e mais um monte de outras coisas.

Mesmo nos 3 cenários acima, se você recai nos casos 1 e 3, o problema provavelmente não é o uso de funções e sim a forma como estas estão organizadas.

Se você recair no caso 2, você provavelmente não estará pensando em otimizações relacionadas ao tamanho da pilha de execução, e sim em espremer o máximo possível de bits no menor espaço possível olhando para o projeto no nível do hardware, considerando coisas como posicionamento de transistores em um microchip, consumo de energia elétrica, geração de calor, etc.

Novamente, provavelmente o seu caso não é nenhum desses.

E para ter certeza, se a conversa de fato chegar no desempenho/performance como muleta ou desculpa esfarrapada para mexer ou não mexer no código, é importante realizar-se medições. Estimar desempenho e realizar-se otimizações é uma coisa que requer evidência, provas e experimentos realizados com rigor metodológico, ou seja é ciência. Ir simplesmente no achismo de desobedecer boas práticas porque isso supostamente produziria um desempenho é falacioso e temerário.

Além disso, a maioria dos problemas referentes a desempenho que existem no mundo real (e não os problemas imaginários inventados por quem não quer seguir boas práticas de programação) não têm qualquer relação com o fato de você estar ou não realizando chamadas de funções ou procedimentos. São problemas que em geral envolvem projetos de algoritmos, organização dos dados na memória ou no disco, threads, cacheamento de informações, sistemas distribuídos, processos síncronos vs assíncronos, e outras coisas que são referentes a arquitetura do projeto, e não a pequenos detalhes referentes ao tamanho da pilha de execução ao se realizar 10 chamadas a uma função.

Sugiro fazer testes em looping medindo o tempo total de no mínimo 1 milhão de iterações.

Mesmo em linguagens de alto nível, conforme mencionado, como Java, na qual tenho razoável experiência, o tempo total das chamadas, sem operações de I/O não deve chegar perto de 1 segundo.

Sobre empilhar muitos dados na stack, com o poder computacional atual isto é irrelevante. Já desenvolvi em linguagens onde a preocupação com o tamanho dos registros de dados gravados era muito grande, já que no passado as ofertas de armazenamento eram muito inferiores. Porém até hoje, jamais tinha visto alguém dar atenção ao custo de chamada de funções em detrimento da qualidade do código e, principalmente, facilidade de manutenção e evolução.

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *