Понимание различных вариантов генерации кода среды выполнения на C# (Roslyn, CodeDom, Linq Expressions,?)

Я работаю над приложением, в котором хочу динамически генерировать код для числового расчета (для производительности). Выполнение этого расчета как операции, управляемой данными, слишком медленно. Чтобы описать мои требования, рассмотрим этот класс:

class Simulation
{
    Dictionary<string, double> nodes;

    double t, dt;

    private void ProcessOneSample()
    {
        t += dt;
        // Expensive operation that computes the state of nodes at the current t.
    }

    public void Process(int N, IDictionary<string, double[]> Input, IDictionary<string, double[]> Output)
    {
        for (int i = 0; i < N; ++i)
        {
            foreach (KeyValuePair<string, double[]> j in Input)
                nodes[j.Key] = j.Value[i];
            ProcessOneSample();
            foreach (KeyValuePair<string, double[]> j in Output)
                j.Value[i] = nodes[j.Key];
        }    
    }
}

Что я хочу сделать, так это JIT-компилировать функцию, которая реализует внешний цикл в процессе. Код, определяющий эту функцию, будет создан на основе данных, которые в настоящее время используются для реализации ProcessOneSample. Чтобы уточнить, что я ожидаю, я ожидаю, что все поиски по словарю будут выполняться один раз в процессе компиляции (т. е. JIT-компиляция будет напрямую связываться с соответствующим объектом в словаре), так что когда скомпилированный код фактически выполняется, как если бы все поиски были жестко запрограммированы.

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

  • Используйте Рослин. Текущий камень преткновения заключается в том, как связать выражения в синтаксисе с переменными-членами из основной программы (т.е. значениями в словаре «состояние»). Возможно ли это?
  • Используйте выражения LINQ (Expression.Compile).
  • Используйте Коддом. Только недавно стало известно об этом в моем поиске Google, и что вызвало этот вопрос. Я не в восторге от того, что спотыкаюсь о третий фреймворк компиляции в .Net.
  • Мой первоначальный план, прежде чем я узнал, что существует любой из этих инструментов, состоял в том, чтобы вызвать собственный код (x86), который я скомпилировал самостоятельно. У меня есть некоторый опыт в этом, но здесь много неизвестных, которые я еще не решил. Это также мой резервный вариант, если производительности вышеперечисленных вариантов недостаточно. Я бы предпочел одно из трех вышеперечисленных решений, потому что я уверен, что они будут намного проще, если я смогу заставить одно из них работать!

Есть ли у кого-нибудь опыт с чем-то подобным, которым они могли бы поделиться?


person dsharlet    schedule 29.09.2013    source источник
comment
Я не понимаю, что вы будете вручную JIT'ировать. х86 ассемблер? Какой аромат? Я думаю, вам лучше отказаться от этой идеи ..   -  person Simon Whitehead    schedule 29.09.2013
comment
@SimonWhitehead Да, я мог бы сгенерировать собственную функцию для реализации поведения, описанного в методе Process. Это не мой основной вариант, я бы предпочел сначала заставить что-то работать с одним из вариантов .Net. Я буду использовать этот вариант только в том случае, если производительность окажется проблемой при выборе .Net.   -  person dsharlet    schedule 29.09.2013
comment
Вы уверены, что это лучший способ ускорить ваш код? Измеряли ли вы производительность, чтобы увидеть, действительно ли проблемы с доступом к словарю? Рассматривали ли вы изменение интерфейса (возможно, использовать набор объектов с double свойствами)?   -  person svick    schedule 29.09.2013
comment
@svick Устранение поиска в словаре - не единственное, что я пытаюсь сделать, это было задумано как пример того, как я хочу, чтобы скомпилированный код выглядел. Еще одно большое преимущество, которое я ожидаю от компиляции кода в ProcessOneSample, — это возможность генерировать код, жестко закодированный для конкретной системы. Одним из примеров того, почему это поможет, является то, что при численном решении систем уравнений я смогу удалить все вычисления с использованием нулей во время компиляции, которые в настоящее время должны оцениваться для каждой выборки в подходе, управляемом данными.   -  person dsharlet    schedule 29.09.2013
comment
@svick Я не ожидаю, что интерфейс будет проблемой, потому что после того, как схема компиляции заработает, для обработки тысяч образцов потребуется всего ~ 10 секунд поиска в словаре, причем для вычисления каждого образца требуются тысячи FLOP. Я также рассматриваю гибкость этого интерфейса как оптимизацию: иногда приложение не будет заботиться о состоянии всего, кроме выходного узла. В этом случае не имеет смысла хранить состояние всех узлов в буфере. Но иногда приложению требуется, чтобы симуляция сохраняла состояние других узлов, кроме выходного узла.   -  person dsharlet    schedule 29.09.2013


Ответы (1)


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

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

Ваш первый вариант — Reflection.Emit, особенно Динамический метод. Reflection.Emit — это довольно низкоуровневый API, и он довольно эффективен (т. е. генерация кода имеет хорошую производительность). Кроме того, поскольку у вас есть полный контроль над генерируемым кодом, у вас есть возможность генерировать наиболее эффективный код (или, очевидно, генерировать очень плохой код). Кроме того, вы не ограничены тем, что позволяет вам делать такой язык, как C#, вся мощь CLR у вас под рукой. Самая большая проблема с Reflection.Emit — это большой объем кода, который необходимо написать, и для этого требуется глубокое знание IL. Писать этот код непросто, как и потом читать или поддерживать его.

Linq.Expressions, точнее метод компиляции предоставляет хорошую альтернативу. Вы можете думать об этом как о типобезопасной оболочке вокруг генерации DynamicMethod с Reflection.Emit. При генерации кода возникают некоторые накладные расходы, что, вероятно, не будет большой проблемой. Что касается свободы самовыражения, вы можете делать практически все, что можно делать в обычном методе C#. У вас нет полного контроля над сгенерированным кодом, но качество в целом очень хорошее. Самым большим преимуществом этого подхода является то, что с его помощью гораздо проще писать и читать программу.

Что касается Roslyn, у вас есть возможность создать синтаксическое дерево или создать C# (или VB) и преобразовать его в синтаксическое дерево для компиляции. Пока рано предполагать, какой может быть производительность, поскольку у нас нет доступного производственного кода (на момент написания). Очевидно, что синтаксический анализ синтаксического дерева будет иметь некоторые накладные расходы, и если вы создаете один метод, способность Roslyn генерировать несколько методов параллельно не сильно поможет. Тем не менее, использование Roslyn позволяет создавать очень читаемые программы.

Что касается CodeDom, я бы не рекомендовал его. Это очень старый API, который (в текущей реализации) запускает процесс CSC.exe для компиляции вашего кода. Я также считаю, что он не поддерживает весь язык C#.

person Kris Vandermotten    schedule 14.10.2013