Pergunta

Minha função será chamada milhares de vezes.Se eu quiser torná-lo mais rápido, alterar as variáveis ​​​​da função local para estática terá alguma utilidade?Minha lógica por trás disso é que, como as variáveis ​​estáticas são persistentes entre as chamadas de função, elas são alocadas apenas na primeira vez e, portanto, cada chamada subsequente não alocará memória para elas e se tornará mais rápida, porque a etapa de alocação de memória não é concluída.

Além disso, se o que foi dito acima for verdade, o uso de variáveis ​​globais em vez de parâmetros seria mais rápido para passar informações para a função toda vez que ela fosse chamada?acho que espaço para parâmetros também é alocado em cada chamada de função, para permitir a recursão (é por isso que a recursão consome mais memória), mas como minha função não é recursiva e se meu raciocínio estiver correto, retirar os parâmetros fará, em teoria, mais rápido.

Eu sei que essas coisas que quero fazer são hábitos horríveis de programação, mas, por favor, diga-me se é sensato.Vou tentar de qualquer maneira, mas por favor me dê sua opinião.

Foi útil?

Solução

A sobrecarga das variáveis ​​locais é zero.Cada vez que você chama uma função, você já está configurando a pilha para os parâmetros, valores de retorno, etc.Adicionar variáveis ​​locais significa que você está adicionando um número um pouco maior ao ponteiro da pilha (um número que é calculado em tempo de compilação).

Além disso, as variáveis ​​locais são provavelmente mais rápidas devido à localidade do cache.

Se você está chamando sua função apenas "milhares" de vezes (não milhões ou bilhões), então você deve observar seu algoritmo em busca de oportunidades de otimização depois você executou um criador de perfil.


Ré:localidade do cache (leia mais aqui):Variáveis ​​globais acessadas com frequência provavelmente têm localidade temporal.Eles também podem ser copiados para um registro durante a execução da função, mas serão gravados de volta na memória (cache) após o retorno da função (caso contrário, eles não seriam acessíveis a mais nada;registradores não possuem endereços).

Variáveis ​​locais geralmente terão localidade temporal e espacial (elas conseguem isso em virtude de serem criadas na pilha).Além disso, eles podem ser “alocados” diretamente nos registradores e nunca gravados na memória.

Outras dicas

A melhor maneira de descobrir é executar um criador de perfil.Isso pode ser tão simples quanto executar vários testes cronometrados usando ambos os métodos e, em seguida, calcular a média dos resultados e compará-los, ou você pode considerar uma ferramenta de criação de perfil completa que se anexa a um processo e representa graficamente o uso da memória ao longo do tempo e a velocidade de execução.

Não execute o ajuste aleatório do microcódigo porque você tem a sensação de que será mais rápido.Todos os compiladores têm implementações de coisas ligeiramente diferentes e o que é verdadeiro em um compilador em um ambiente pode ser falso em outra configuração.

Para resolver esse comentário sobre menos parâmetros:o processo de "inlining" de funções remove essencialmente a sobrecarga relacionada à chamada de uma função.Provavelmente, uma pequena função será automaticamente incorporada pelo compilador, mas você pode sugerir que uma função seja incorporada também.

Em uma linguagem diferente, C++, o novo padrão lançado suporta encaminhamento perfeito e semântica de movimento perfeito com referências de valor, o que elimina a necessidade de temporários em certos casos, o que pode reduzir o custo de chamar uma função.

Suspeito que você esteja otimizando prematuramente; no entanto, você não deve se preocupar tanto com o desempenho até descobrir seus verdadeiros gargalos.

Absolutamente não!A única diferença de “desempenho” é quando as variáveis ​​são inicializadas

    int anint = 42;
 vs
    static int anint = 42;

No primeiro caso, o número inteiro será definido como 42 toda vez que a função for chamada; no segundo caso, será definido como 42 quando o programa for carregado.

No entanto, a diferença é tão trivial que quase não se nota.É um equívoco comum pensar que o armazenamento deve ser alocado para variáveis ​​"automáticas" em cada chamada.Não é assim que C usa o espaço já alocado na pilha para essas variáveis.

Variáveis ​​estáticas podem realmente atrasá-lo, pois algumas otimizações agressivas não são possíveis em variáveis ​​estáticas.Além disso, como os locais estão em uma área contígua da pilha, é mais fácil armazená-los em cache com eficiência.

Não há uma resposta para isso.Isso irá variar de acordo com a CPU, o compilador, os sinalizadores do compilador, o número de variáveis ​​locais que você possui, o que a CPU está fazendo antes de você chamar a função e, possivelmente, a fase da lua.

Considere dois extremos;se você tiver apenas uma ou algumas variáveis ​​locais, elas poderão ser facilmente armazenadas em registros, em vez de serem alocados em locais de memória.Se a "pressão" do registro for suficientemente baixa, isso poderá acontecer sem a execução de nenhuma instrução.

No extremo oposto, existem algumas máquinas (por exemplo, mainframes IBM) que não possuem nenhuma pilha.Nesse caso, o que normalmente consideraríamos como quadros de pilha são, na verdade, alocados como uma lista vinculada no heap.Como você provavelmente deve imaginar, isso pode ser bastante lento.

Quando se trata de acessar as variáveis, a situação é um tanto semelhante - é garantido que o acesso a um registrador de máquina seja mais rápido do que qualquer coisa alocada na memória pode ser esperada.OTOH, é possível que o acesso às variáveis ​​na pilha seja bastante lento - normalmente requer algo como um acesso indireto indexado, que (especialmente com CPUs mais antigas) tende a ser bastante lento.OTOH, o acesso a um global (que é estático, mesmo que seu nome não seja globalmente visível) normalmente requer a formação de um endereço absoluto, que algumas CPUs também penalizam até certo ponto.

Conclusão:até mesmo o conselho para criar o perfil do seu código pode ser equivocado - a diferença pode facilmente ser tão pequena que nem mesmo um criador de perfil o detectará de forma confiável, e o apenas Uma maneira de ter certeza é examinar a linguagem assembly produzida (e passar alguns anos aprendendo linguagem assembly bem o suficiente para saber dizer qualquer coisa quando você fazer olhe para isso).O outro lado disso é que quando você está lidando com uma diferença que você nem consegue medir de forma confiável, as chances de que ela tenha um efeito material na velocidade do código real são tão remotas que provavelmente não vale a pena.

Parece que o estático versus não estático foi completamente abordado, mas no tópico de variáveis ​​globais.Freqüentemente, isso retardará a execução de um programa em vez de acelerá-la.

A razão é que variáveis ​​com escopo restrito facilitam a otimização pesada do compilador; se o compilador precisar procurar em todo o seu aplicativo instâncias em que um global possa ser usado, sua otimização não será tão boa.

Isso é agravado quando você introduz ponteiros, digamos que você tenha o seguinte código:

int myFunction()
{
    SomeStruct *A, *B;
    FillOutSomeStruct(B);
    memcpy(A, B, sizeof(A);
    return A.result;
}

o compilador sabe que os ponteiros A e B nunca podem se sobrepor e, portanto, pode otimizar a cópia.Se A e B forem globais, eles poderão apontar para memória sobreposta ou idêntica, isso significa que o compilador deve 'jogar pelo seguro', o que é mais lento.O problema é geralmente chamado de 'aliasing de ponteiro' e pode ocorrer em muitas situações, não apenas em cópias de memória.

http://en.wikipedia.org/wiki/Pointer_alias

Sim, o uso de variáveis ​​estáticas tornará a função um pouco mais rápida.No entanto, isso causará problemas se você quiser tornar seu programa multithread.Como as variáveis ​​estáticas são compartilhadas entre invocações de funções, invocar a função simultaneamente em diferentes threads resultará em um comportamento indefinido.Multi-threading é o tipo de coisa que você pode querer fazer no futuro para realmente acelerar seu código.

A maioria das coisas que você mencionou são chamadas de microotimizações.Geralmente, preocupar-se com esse tipo de coisa é uma péssima ideia.Isso torna seu código mais difícil de ler e mais difícil de manter.Também é altamente provável que introduza bugs.Você provavelmente obterá mais retorno do seu investimento fazendo otimizações em um nível superior.

Como sugere o M2tM, executar um criador de perfil também é uma boa ideia.Confira gprof para um que é bastante fácil de usar.

Você sempre pode cronometrar seu aplicativo para realmente determinar o que é mais rápido.Aqui está o que eu entendo:(tudo isso depende da arquitetura do seu processador, aliás)

As funções C criam um quadro de pilha, que é onde os parâmetros passados ​​são colocados e as variáveis ​​locais são colocadas, bem como o ponteiro de retorno para onde o chamador chamou a função.Não há alocação de gerenciamento de memória aqui.Geralmente é um simples movimento do ponteiro e pronto.Acessar dados da pilha também é bastante rápido.As penalidades geralmente entram em jogo quando você está lidando com ponteiros.

Quanto às variáveis ​​globais ou estáticas, elas são iguais...desde que serão alocadas na mesma região da memória.Acessá-los pode usar um método de acesso diferente das variáveis ​​locais, dependendo do compilador.

A principal diferença entre seus cenários é o consumo de memória, e não tanta velocidade.

O uso de variáveis ​​estáticas pode tornar seu código significativamente Mais devagar.Variáveis ​​estáticas devem existir em uma região de 'dados' da memória.Para usar essa variável, a função deve executar uma instrução de carregamento para ler da memória principal ou uma instrução de armazenamento para escrever nela.Se essa região não estiver no cache, você perderá muitos ciclos.Uma variável local que reside na pilha certamente terá um endereço que está no cache e pode até estar em um registro da CPU, nunca aparecendo na memória.

Concordo com os outros comentários sobre criação de perfil para descobrir coisas assim, mas de modo geral, variáveis ​​estáticas de função devem ser mais lentas.Se você os deseja, o que você realmente procura é um global.A estática da função insere código/dados para verificar se a coisa já foi inicializada e é executada toda vez que sua função é chamada.

O perfil pode não ver a diferença, desmontar e saber o que procurar pode.

Eu suspeito que você obterá apenas uma variação de alguns ciclos de clock por loop (em média, dependendo do compilador, etc.).Às vezes, a mudança será uma melhoria dramática ou dramaticamente mais lenta, e isso não será necessariamente porque a casa das variáveis ​​foi movida de/para a pilha.Digamos que você economize quatro ciclos de clock por chamada de função para 10.000 chamadas em um processador de 2 GHz.Cálculo muito aproximado:20 microssegundos salvos.20 microssegundos são muito ou pouco comparados ao seu tempo de execução atual?

Você provavelmente obterá mais melhoria de desempenho transformando todos os seus caracteres e variáveis ​​​​short em ints, entre outras coisas.É bom saber micro-otimização, mas leva muito tempo experimentando, desmontando, cronometrando a execução do seu código, entendendo que menos instruções não significa necessariamente mais rápido, por exemplo.

Pegue o seu programa específico, desmonte a função em questão e o código que a chama.Com e sem estática.Se você ganhar apenas uma ou duas instruções e esta for a única otimização que vai fazer, provavelmente não vale a pena.Talvez você não consiga ver a diferença durante a criação de perfil.Mudanças no local onde as linhas de cache são atingidas podem aparecer na criação de perfil antes de mudanças no código, por exemplo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top