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

StackOverflow https://stackoverflow.com/questions/1359533

Вопрос

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

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


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;
        }
    }
Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top