Скомпилированные выражения работают намного медленнее, чем интерпретированные версии - PullRequest
2 голосов
/ 11 мая 2011

У меня есть движок правил, который поддерживает два режима работы:

  1. Компиляция в программу на C # и добавление в движок
  2. Разбор в инструкции на основе стека с обратной полировкой и интерпретация

Правила - это простые арифметические выражения с вызовами функций (max, min, sin, cos и т. Д.)

Я бы предположил, что скомпилированная версия (т. Е. # 1) будет намного быстрее интерпретируемой версии (т. Е. # 2) - фактически, это основная причина наличия скомпилированного режима в первое место. Однако мои тесты скорости показали обратное.

Скомпилированная версия

Action<double>[] Rules = new[] { calc1, calc2, calc3 ... };
double[] v = new double[...];   // Variables

void calc1(double arg) { v[3]=v[12]+v[15]/v[20] };   // "x3=x12+x15/x20"
void calc2(double arg) { ... };
   :
// Start timer now
Rules.AsParallel().ForAll(r => r(...));
// End timer

Интерпретированная версия

Expression[] Rules = ...
// Each rule is already parsed into an Expression object, which is a set of
// reverse-polish stack-based instructions.
// For example, "x3=x12+x15/x20" will be parsed to:
//     [ Push(12), Push(15), Push(20), Divide(), Add() ]
// Start timer now
Rules.AsParallel().ForAll(r => r.Evaluate(...));
// End timer

Здесь «Выражение» является частью сторонней библиотеки, которая анализирует простую строку в простой набор инструкций обратного полирования стека, которые затем можно интерпретировать. Это не объект дерева выражений в LINQ - просто для пояснения.

Примечание: не беспокойтесь о параллелизме, поскольку в реальном коде я сортирую правила по «слоям» и последовательно вычисляю слои, каждый из которых зависит только от значений, рассчитанных в предыдущих слоях. Оба режима имеют одинаковую структуру слоев.

Результаты

Шокирующе, интерпретированная версия работает НАМНОГО быстрее, чем скомпилированная версия, в среднем в 4 раза! Другими словами, скомпилированной версии потребовалось 0,3 с, чтобы пройти около 1200 правил, в то время как интерпретированной версии потребовалось в среднем 0,08-0,1 с.

Мой компьютер - так себе двухъядерный Core2.

Я использую .NET 4.0, Visual Studio 10.

Производительность аналогична в сборках Debug или Release.

Мой вопрос

Что может быть причиной значительного замедления в скомпилированном режиме?

ПРИМЕЧАНИЕ: я разместил один возможный ответ

1 Ответ

1 голос
/ 11 мая 2011

.NET - это JIT-скомпилированная среда, поэтому чем больше кода для JIT-компиляции, тем медленнее она.Может случиться так, что 1200 методов JIT-компилируются во время выполнения в момент выполнения, тогда как в интерпретируемом режиме только JIT-компилируется только интерпретатор, один раз.Возможно, я вижу дополнительные циклы JIT в своих циклах для скомпилированного режима.

Эксперимент:

  1. Запуск каждого режима 5 раз (только для завершения любой JIT-компиляции и заполнения кэшей)
  2. Время 50 запусков, среднее значение

Результаты:

  1. Скомпилированный режим: 1,6 мс на цикл
  2. Интерпретированный режим: 5,3 мс наrun

Наблюдения:

Похоже, что во время прогона скомпилированного режима FIRST было потрачено много времени.

SECOND запуск скомпилированного режима уже по скорости аналогичен интерпретируемому режиму.

Интерпретируемый режим не ускоряется значительно с количеством прогонов.

Таким образом, предлагаямоя точка зрения, что код правила JIT-компилируется во время первого запуска.

...