Pergunta

Tenho uma idéia de como posso melhorar o desempenho com a geração dinâmica de código, mas não tenho certeza de qual é a melhor maneira de abordar esse problema.

Suponha que eu tenha uma aula


class Calculator
{
  int Value1;
  int Value2;
  //.......... 
  int ValueN;

  void DoCalc()
  {
    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    //....
    //....
    //....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }
}

O método docalc está no nível mais baixo e é chamado muitas vezes durante o cálculo. Outro aspecto importante é que o valor é definido apenas no início e não muda durante o cálculo. Muitos dos IFs no método DOCALC são desnecessários, pois muitos dos valuen são 0. Então, eu esperava que a geração dinâmica de código pudesse ajudar a melhorar o desempenho.

Por exemplo, se eu criar um método


  void DoCalc_Specific()
  {
    const Value1 = 0;
    const Value2 = 0;
    const ValueN = 1;

    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    ....
    ....
    ....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }

e compilá -lo com otimizações ligadas no compilador C# é inteligente o suficiente para manter apenas as coisas necessárias. Então, eu gostaria de criar esse método no tempo de execução com base nos valores do valuen e usar o método gerado durante os cálculos.

Eu acho que eu poderia usar árvores de expressão para isso, mas as árvores de expressão funcionam apenas com funções simples de lambda, para que eu não possa usar coisas como se, enquanto etc. dentro do corpo da função. Portanto, neste caso, preciso alterar esse método de maneira apropriada.

Outra possibilidade é criar o código necessário como uma string e compilá -lo dinamicamente. Mas seria muito melhor para mim se eu pudesse pegar o método existente e modificá -lo de acordo.

Também há reflexão.

POR FALAR NISSO. Não estou restrito a C#. Portanto, estou aberto a sugestões de linguagens de programação mais adequadas para esse tipo de problema. Exceto por Lisp por alguns motivos.

Um esclarecimento importante. DoValue1RelatedStuff () não é uma chamada de método no meu algoritmo. É apenas um cálculo baseado em fórmula e é muito rápido. Eu deveria ter escrito assim


if (Value1 > 0)
{
  // Do Value1 Related Stuff
}

Eu executei alguns testes de desempenho e posso ver isso com dois IFs quando um é desativado, o método otimizado é cerca de 2 vezes mais rápido do que com o redundante se.

Aqui está o código que usei para testar:


    public class Program
    {
        static void Main(string[] args)
        {
            int x = 0, y = 2;

            var if_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                WithIf(x, y);
            }
            var if_et = DateTime.Now.Ticks - if_st;
            Console.WriteLine(if_et.ToString());

            var noif_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                Without(x, y);
            }
            var noif_et = DateTime.Now.Ticks - noif_st;
            Console.WriteLine(noif_et.ToString());

            Console.ReadLine();

        }

        static double WithIf(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i  < 100; i++)
            {
                if (x > 0)
                {
                    result += x * 0.01;
                }
                if (y > 0)
                {
                    result += y * 0.01;
                }
            }
            return result;
        }

        static double Without(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i < 100; i++)
            {
                result += y * 0.01;
            }
            return result;
        }
    }
Foi útil?

Solução

Normalmente, eu nem pensava em tal otimização. Quanto trabalho faz DoValueXRelatedStuff() Faz? Mais de 10 a 50 ciclos de processador? Sim? Isso significa que você criará um sistema bastante complexo para economizar menos de 10% de tempo de execução (e isso me parece bastante otimista). Isso pode cair facilmente para menos de 1%.

Não há espaço para outras otimizações? Melhores algoritmos? Um você realmente precisa eliminar ramos únicos, levando apenas um único ciclo de processador (se a previsão da ramificação estiver correta)? Sim? Você não deveria pensar em escrever seu código no assembler ou em algo mais específico da máquina em vez de usar .NET?

Você poderia dar a ordem de N, a complexidade de um método típico e a proporção de expressões geralmente avaliando para verdadeiro?

Outras dicas

Surpreenderia -me encontrar um cenário em que a sobrecarga de avaliar o E se As declarações valem o esforço para emitir o código dinamicamente.

Suporte moderno da CPU Previsão de ramificação e Predicação de ramificação, o que faz com que a sobrecarga para ramificações em pequenos segmentos de código se aproximem de zero.

Você já tentou comparar duas versões codificadas à mão do código, uma que possui todas as estratégias IF em vigor, mas fornece zero valores para a maioria e que remove as ramificações de todas as mesmas se?

Se você gosta realmente de otimização de código - antes de fazer qualquer coisa - execute o Profiler! Ele mostrará onde está o gargalo e quais áreas valem a pena otimizar.

Além disso - se a escolha do idioma não for limitada (exceto LISP), nada vencerá o assembler em termos de desempenho;)

Lembro -me de alcançar alguma mágica de desempenho reescrevendo algumas funções internas (como a que você tem) usando o assembler.

Antes de fazer qualquer coisa, você realmente tem um problema?

ou seja, isso dura o tempo suficiente para incomodá -lo?

Nesse caso, descubra o que realmente está levando tempo, não o que você adivinhe. este é o método rápido, sujo e altamente eficaz que uso para ver para onde vai o tempo.

Agora, você está falando sobre interpretar versus compilar. O código interpretado é tipicamente 1-2 ordens de magnitude mais lentamente que o código compilado. O motivo é que os intérpretes estão continuamente descobrindo o que fazer a seguir, e então esquecendo, enquanto código compilado apenas sabe.

Se você estiver nessa situação, pode fazer sentido pagar o preço da tradução para obter a velocidade do código compilado.

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