Динамическая компиляция для производительности

У меня есть представление о том, как улучшить производительность с помощью динамической генерации кода, но я не уверен, что это лучший способ решить эту проблему.

Предположим, у меня есть класс


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

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

Метод DoCalc находится на самом низком уровне и вызывается много раз во время вычислений. Другим важным аспектом является то, что ValueN устанавливаются только в начале и не изменяются во время расчета. Многие из if в методе DoCalc не нужны, так как многие из ValueN равны 0. Поэтому я надеялся, что динамическое генерирование кода может помочь повысить производительность.

Например, если я создаю метод


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

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

и скомпилируйте его с включенными оптимизациями, компилятор C# достаточно умен, чтобы хранить только необходимые вещи. Поэтому я хотел бы создать такой метод во время выполнения на основе значений ValueN и использовать сгенерированный метод во время вычислений.

Я думаю, что я мог бы использовать для этого деревья выражений, но деревья выражений работают только с простыми лямбда-функциями, поэтому я не могу использовать такие вещи, как if, while и т. Д. Внутри тела функции. Так что в этом случае мне нужно изменить этот метод соответствующим образом.

Другая возможность - создать необходимый код в виде строки и динамически скомпилировать его. Но для меня было бы намного лучше, если бы я мог воспользоваться существующим методом и соответствующим образом изменить его.

Также есть Reflection.Emit, но я не хочу придерживаться этого, так как его будет очень сложно поддерживать.

КСТАТИ. Я не ограничен C#. Так что я открыт для предложений языков программирования, которые лучше всего подходят для такого рода проблем. За исключением LISP по нескольким причинам.

Одно важное уточнение. DoValue1RelatedStuff() не является вызовом метода в моем алгоритме. Это просто расчет на основе формул, и он довольно быстрый. Я должен был написать это так


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

Я провел несколько тестов производительности и вижу, что с двумя if, если один отключен, оптимизированный метод примерно в 2 раза быстрее, чем с избыточным if.

Вот код, который я использовал для тестирования:


    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;
        }
    }

4 ответа

Решение

Я бы даже не думал о такой оптимизации. Сколько работы делает DoValueXRelatedStuff() делать? Более 10-50 циклов процессора? Да? Это означает, что вы собираетесь построить довольно сложную систему, чтобы сэкономить менее 10% времени на выполнение (и это кажется мне довольно оптимистичным). Это может легко снизиться до менее чем 1%.

Нет ли места для других оптимизаций? Лучшие алгоритмы? Действительно ли вам нужно исключить отдельные ветви, занимающие только один цикл процессора (если прогноз ветви верен)? Да? Разве вы не должны думать о написании своего кода на ассемблере или чем-то еще более специфичном для машины вместо использования.NET?

Не могли бы вы дать заказ N, сложность типичного метода и отношение выражений, обычно оценивающих к истинным?

Если вы действительно занимаетесь оптимизацией кода - прежде чем что-то делать - запустите профилировщик! Он покажет вам, где находится узкое место и какие области стоит оптимизировать.

Также - если выбор языка не ограничен (кроме LISP), то ничто не сравнится с ассемблером с точки зрения производительности;)

Я помню, как достиг магии производительности, переписав некоторые внутренние функции (например, ту, что у вас есть), используя ассемблер.

Я был бы удивлен, обнаружив сценарий, в котором затраты на оценку операторов if стоят усилий для динамического генерирования кода.

Современный CPU поддерживает предсказание ветвлений и предикаты ветвлений, что сводит к нулю издержки для ветвей в небольших сегментах кода.

Пытались ли вы сравнить две версии кода, написанные вручную, одна из которых содержит все операторы if, но для большинства из них нулевые значения, а другая удаляет все эти ветви if?

Прежде чем что-то делать, у вас действительно есть проблемы?

то есть он работает достаточно долго, чтобы беспокоить вас?

Если это так, выясните, что на самом деле занимает время, а не то, что вы предполагаете. Это быстрый, грязный и очень эффективный метод, который я использую, чтобы увидеть, куда уходит время.

Теперь вы говорите о интерпретации против компиляции. Интерпретируемый код обычно на 1-2 порядка медленнее, чем скомпилированный код. Причина в том, что интерпретаторы постоянно выясняют, что делать дальше, а потом забывают, а скомпилированный код просто знает.

Если вы находитесь в такой ситуации, то, возможно, имеет смысл заплатить цену за перевод, чтобы получить скорость скомпилированного кода.

Другие вопросы по тегам