A programação modular afeta o tempo de computação?

19

Todo mundo diz que devo fazer meu código modular, mas não é menos eficiente se eu usar mais chamadas de método em vez de menos, mas maiores, métodos? Qual é a diferença em Java, C ou C ++ para esse assunto?

Entendo que é mais fácil editar, ler e entender, especialmente em um grupo. Portanto, a perda de tempo de computação é insignificante em comparação com os benefícios de limpeza do código?

fatsokol
fonte
2
A questão é quanto tempo levará para o tempo de processamento economizado, passando o tempo gasto em manutenção mais difícil. A resposta para isso depende inteiramente da sua aplicação.
Blrfl
2
Muitas boas perguntas geram algum grau de opinião com base na experiência de especialistas, mas as respostas a essa pergunta tendem a ser quase inteiramente baseadas em opiniões, e não em fatos, referências ou conhecimentos específicos.
Gnat 29/07
10
Também vale ressaltar que a penalidade de computação para uma chamada de função ou método é tão minúscula que, mesmo em um programa muito grande com muitas funções e chamadas de método, essas chamadas nem se classificam no gráfico.
28613 greyfade
1
@greyfade: Isso é verdade para os saltos diretos, mas o salto predito indireto adicional pode custar, por exemplo, ~ 3% do tempo total de execução do programa (apenas um número do programa que eu verifiquei recentemente - talvez não tenha sido representativo). Dependendo da sua área, você pode ou não considerá-lo significativo, mas ele se registrou no gráfico (e é claro que é pelo menos parcialmente ortogonal à modularidade).
Maciej Piechotka
4
Otimização prematura é a raiz de todo o mal. O código linear é um pouco mais rápido que o código modular. O código modular é muito mais rápido que o código espaguete. Se você pretende um código linear sem um projeto muito (MUITO) completo de tudo isso, vai acabar com um código de espaguete, eu garanto isso.
SF.

Respostas:

46

Sim, é irrelevante.

Os computadores são mecanismos de execução incansáveis ​​e quase perfeitos, trabalhando a velocidades totalmente incomparáveis ​​aos do cérebro. Embora exista uma quantidade mensurável de tempo que uma chamada de função adiciona ao tempo de execução de um programa, isso não é nada comparado ao tempo adicional necessário para o cérebro da próxima pessoa envolvida com o código quando eles precisam desemaranhar a rotina ilegível até começar a entender como trabalhar com isso. Você pode tentar o cálculo de uma piada - suponha que seu código tenha que ser mantido apenas uma vez e que adicione apenas meia hora ao tempo necessário para que alguém chegue a um acordo com o código. Pegue a velocidade do clock do processador e calcule: quantas vezes o código precisaria executar para sonhar em compensar isso?

Em resumo, sentir pena da CPU é completamente, totalmente equivocado 99,99% do tempo. Para os casos raros restantes, use criadores de perfil. Você não assuma que você pode detectar esses casos - você não pode.

Kilian Foth
fonte
2
Embora eu concorde que na maioria das vezes seja otimização prematura, acho que seu argumento com o tempo é ruim. O tempo é relativo em diferentes situações e você não pode simplesmente calcular como fez.
Honza Brabec
28
+1 apenas para a expressão "ter pena da CPU", porque acentua muito bem o erro na otimização prematura.
Michael Borgwardt 29/07
4
Altamente relacionado: Qual a velocidade dos computadores no dia a dia? Sair do seu caminho para evitar algumas chamadas de função para "velocidade" é como sair do seu caminho para salvar alguém em um minuto no total ao longo de sua vida inteira . Resumindo: nem vale a pena o tempo necessário para considerar.
BlueRaja - Danny Pflughoeft 29/07
7
+1 por vender de maneira tão eloquente o que está faltando, mas você esqueceu de adicionar o peso mais importante à balança que torna a pena da CPU ainda mais grave: desconsiderando o dinheiro perdido e o tempo gasto nessa manutenção única; se demorou 1 segundo, ainda há uma pia muito mais insidiosa lá. Risco de insetos . Quanto mais confuso e difícil for para o mantenedor posterior alterar seu código, maior o risco de ele implementar bugs nele, o que pode causar custos inevitáveis ​​intransponíveis de risco para os usuários e qualquer bug causa manutenção subseqüente (o que pode causar bugs ...)
Jimmy Hoffa
Um antigo professor meu costumava dizer: "Se você está pensando se está otimizando prematuramente ou não, pare aqui. Se essa era a escolha certa, você saberia".
Ven
22

Depende.

No mundo glacialmente lento que é a programação da Web, onde tudo acontece em velocidades humanas, a programação pesada de métodos, em que o custo da chamada de método é comparável ou excede o custo do processamento feito pelo método, provavelmente não importa .

No mundo da programação de sistemas embarcados e manipuladores de interrupções para interrupções de alta taxa, isso certamente importa. Nesse ambiente, os modelos usuais de "acesso à memória são baratos" e "o processador é infinitamente rápido" se decompõem. Vi o que acontece quando um programador orientado a objetos de mainframe escreve seu primeiro manipulador de interrupções de alta taxa. Não foi bonito.

Vários anos atrás, eu estava colorindo blob de conectividade de 8 vias não-recursivas em imagens FLIR em tempo real, no que era naquele momento um processador decente. A primeira tentativa usou uma chamada de sub-rotina e a sobrecarga da chamada de sub-rotina comeu o processador vivo. (4 chamadas POR PIXEL x 64K pixels por quadro x 30 quadros por segundo = você descobre). A segunda tentativa transformou a sub-rotina em uma macro C, sem perda de legibilidade, e tudo foi rosas.

Você precisa olhar muito para o que está fazendo e para o ambiente em que estará fazendo isso.

John R. Strohm
fonte
Lembre-se de que a maioria dos problemas modernos de programação é melhor tratada com muitos códigos orientados a objetos e com métodos adequados. Você saberá quando estiver em um ambiente de sistemas embarcados.
Kevin - Restabelece Monica
4
+1, mas com uma ressalva: geralmente é um caso de um compilador otimizador fazer um trabalho melhor de embutir, desenrolar o loop e fazer otimizações escalares e vetoriais do que uma pessoa. O programador ainda precisa conhecer muito bem a linguagem, o compilador e a máquina para tirar proveito disso, por isso não é mágico.
detly
2
Isso é verdade, mas você otimizou seu código depois de escrever a lógica e criar um perfil para encontrar a parte problemática do seu algoritmo. Você não começou a codificar por desempenho, mas por legibilidade. E as macros ajudarão você a manter seu código legível.
Uwe Plonus
2
Mesmo na programação de sistemas embarcados, comece escrevendo um código correto e claro e só comece a otimizar depois disso, se necessário e conforme orientação de criação de perfil . Caso contrário, é muito fácil colocar muito trabalho que não tem efeito real no desempenho / tamanho do código e que apenas dificulta a compreensão da fonte.
Donal Fellows
1
@detly: Sim e não. Compiladores modernos podem, EM GERAL, fazer um trabalho melhor DENTRO DOS LIMITES IMPOSTOS PELA LÍNGUA. O programador humano sabe se os limites impostos pelo idioma são aplicáveis ​​no caso específico em questão. Por exemplo, os compiladores Cn e C ++ são exigidos pelos padrões de linguagem para permitir que o programador faça algo patológico e gere código que funcione de qualquer maneira. O programador pode saber que ele não é louco, estúpido ou aventureiro o suficiente para fazer essas coisas, e fazer otimizações que de outra forma não seriam seguras, porque ele sabe que elas são seguras nesse caso.
John R. Strohm
11

Primeiro de tudo: os programas em um idioma superior são para serem lidos por seres humanos e não por máquinas.

Então escreva os programas para que você os entenda. Não pense em desempenho (se você realmente tiver problemas de desempenho, crie um perfil de seu aplicativo e aprimore o desempenho onde for necessário).

Mesmo que seja verdade que chamar um método ou função exija alguma sobrecarga, isso não importa. Hoje, os compiladores devem poder compilar seu código em uma linguagem de máquina eficiente, para que o código gerado seja eficiente para a arquitetura de destino. Use as opções de otimização do seu compilador para obter o código eficiente.

Uwe Plonus
fonte
5
Eu diria que deveríamos escrever programas de tal maneira que outros os entendam.
22813 Bartlomiej Lewandowski
A primeira pessoa que precisa ler um programa é o próprio desenvolvedor. Essa é a razão pela qual eu escrevi você . Se outras pessoas puderem ler o programa também é bom, mas (aos meus olhos) não é necessário (em primeiro lugar). Se você trabalha em equipe, outras pessoas também devem entender o programa, mas minha intenção era que essa pessoa tivesse que ler um programa, não computadores.
Uwe Plonus
5

Normalmente, quando você teria uma função grande e a dividiria em várias menores, essas menores serão incorporadas porque a única desvantagem de inlining (repetindo muito as mesmas instruções) não é relevante nesse caso. Isso significa que seu código funcionará como se você tivesse escrito uma função grande.

Se eles não estiverem embutidos por algum motivo e isso se tornar um problema de desempenho, considere a inclusão manual. Nem todos os aplicativos são formulários CRUD em rede com enormes latências intrínsecas.

Esailija
fonte
2

Provavelmente não há custo de computação. Geralmente, os compiladores / JITs nos últimos 10 a 20 anos lidam com a função embutida perfeitamente. Para o C / C ++, geralmente é limitado a funções 'inlináveis' (ou seja, a definição de função está disponível para o compilador durante a compilação - ou seja, está no cabeçalho do mesmo arquivo), mas as técnicas atuais de LTO superam isso.

Se você dedica algum tempo à otimização, depende da área em que está trabalhando. Se você lida com aplicativos 'normais' que passam a maior parte do tempo aguardando entrada - provavelmente não deve se preocupar com otimizações, a menos que o aplicativo 'pareça' lento.

Mesmo nesses casos, você deve se concentrar em muitas coisas antes de fazer a micro-otimização:

  • Onde estão os problemas? Geralmente, as pessoas têm dificuldade em encontrar os pontos de acesso à medida que lemos o código-fonte de maneira diferente. Temos diferentes proporções de tempo de operação e as executamos sequencialmente, enquanto os processadores modernos não .
  • É sempre necessário fazer algum cálculo? Por exemplo, se você alterar um único parâmetro entre milhares, poderá querer calcular apenas uma parte afetada em vez do modelo inteiro.
  • Você usa o algoritmo ideal? Mudar de O(n)para O(log n)pode ter um impacto muito maior do que qualquer coisa que você possa obter com a micro-otimização.
  • Você usa estruturas adequadas? Digamos que você esteja usando um Listquando precisar, HashSetpara ter O(n)pesquisas quando puder O(1).
  • Você usa paralelismo de forma eficiente? Atualmente, mesmo os telefones celulares podem ter 4 núcleos ou mais, pode ser tentador usar threads. No entanto, eles não são uma bala de prata, pois têm custo de sincronização (sem mencionar que, se o problema está ligado à memória, não faz sentido).

Mesmo que você decida executar micro-otimização (o que praticamente significa que seu software é usado em HPC, incorporado ou usado apenas por um número muito grande de pessoas - caso contrário, o custo adicional de manutenção supera os custos de tempo do computador). para identificar os pontos ativos (kernels) que você deseja acelerar. Mas então você provavelmente deveria:

  1. Conheça exatamente a plataforma em que você está trabalhando
  2. Conheça exatamente o compilador em que você está trabalhando e em qual otimização ele é capaz de executar e como escrever código idiomático para habilitá-los.
  3. Pense nos padrões de acesso à memória e quanto você pode caber no cache (o tamanho exato que você conhece no ponto 1).
  4. Então, se você estiver vinculado à computação, pense em reorganizar a computação para salvar o cálculo

Como observação final. Normalmente, o único problema que você tem com as chamadas de método é os saltos indiretos (métodos virtuais) que não foram previstos pelo preditor de ramificação (infelizmente o salto indireto é o caso mais difícil). Contudo:

  • Java possui um JIT que, em muitos casos, pode prever o tipo de classe e, portanto, um alvo de salto antecipado e, portanto, nos pontos de acesso, você não deve ter muitos problemas.
  • Os compiladores C ++ geralmente executam uma análise de programa e, pelo menos em alguns casos, podem prever o destino em tempo de compilação.
  • Nos dois casos em que o objetivo foi previsto, o inlining deve funcionar. Se o compilador não conseguiu realizar as chances de inclusão, também não conseguimos.
Maciej Piechotka
fonte
0

Minha resposta provavelmente não se expandirá muito nas respostas existentes, mas acho que meus dois centavos podem ser úteis.

Primeiramente; Sim, por modularidade, você geralmente abre mão de algum nível de tempo de execução. Escrever tudo no código de montagem fornecerá a melhor velocidade. Dito isto...

Você conhece o YouTube? Provavelmente o site com maior largura de banda existente ou o segundo na Netflix? Eles escrevem uma grande parte de seu código em Python, que é uma linguagem altamente modular, não totalmente construída para um desempenho de alto nível.

O problema é que, quando algo está errado, e os usuários estão reclamando sobre o carregamento lento dos vídeos, não há muitos cenários em que essa lentidão seria atribuída à lenta velocidade de execução do Python. No entanto, a rápida recompilação do Python e sua capacidade modular de tentar coisas novas sem verificação de tipo provavelmente permitirão que os engenheiros depurem o que está errado muito rapidamente ("Uau. Nosso novo estagiário escreveu um loop que faz uma nova subconsulta SQL para TODOS os resultados. ") ou (" Ah, o Firefox descontinuou o antigo formato de cabeçalho de cache; e eles criaram uma biblioteca Python para configurar facilmente o novo ")

Nesse sentido, mesmo em termos de tempo de execução, uma linguagem modular pode ser considerada mais rápida, porque depois que você descobrir quais são seus gargalos, provavelmente ficará mais fácil reorganizar seu código para que ele funcione da melhor maneira. Muitos engenheiros lhe dirão que os resultados de desempenho pesado não estavam onde eles pensavam que estavam (e, na verdade, as coisas que eles otimizaram eram pouco necessárias; ou nem funcionavam da maneira que esperavam!)

Katana314
fonte
0

Sim e não. Como outros já observamos o programa para facilitar a leitura primeiro, depois a eficiência. No entanto, existem práticas padrão que são legíveis e eficientes. A maioria dos códigos é executada com pouca frequência e você não terá muita vantagem em otimizá-lo.

O Java pode incorporar chamadas de função menores, portanto, há poucas razões para evitar a gravação das funções. Os otimizadores tendem a funcionar melhor com códigos mais simples e fáceis de ler. Existem estudos que mostram atalhos que teoricamente deveriam ser executados mais rapidamente, na verdade, levam mais tempo. É provável que o compilador JIT funcione melhor, o código é menor e as peças executadas com frequência podem ser identificadas e otimizadas. Eu não tentei, mas esperaria que uma função grande, chamada relativamente raramente, não fosse compilada.

Provavelmente isso não se aplica ao Java, mas um estudo descobriu que funções maiores na verdade foram mais lentas devido à exigência de um modelo de referência de memória diferente. Isso foi específico do hardware e do otimizador. Para módulos menores, foram utilizadas instruções que funcionavam em uma página de memória. Elas eram mais rápidas e menores do que as instruções necessárias quando a função não se encaixava na página.

Há casos em que vale a pena otimizar o código, mas geralmente é necessário criar um perfil do código para determinar onde está. Acho que muitas vezes não é o código que eu esperava.

BillThor
fonte