DLR и производительность - PullRequest
1 голос
/ 01 февраля 2011

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

Извините, если это долго, но не стесняйтесьчтобы просмотреть общую суть.

Я использую библиотеку IronPython, поскольку она позволяет очень легко определять вычисления.Мой рабочий ноутбук дает производительность около 400 000 вычислений в секунду, выполняя следующие действия:

ScriptEngine py = Python.CreateEngine();
ScriptScope pys = py.CreateScope();

ScriptSource src = py.CreateScriptSourceFromString(@"
def result():
    res = [None]*1000000
    for i in range(0, 1000000):
        res[i] = b.GetValue() + 1
    return res
result()
");

CompiledCode compiled = src.Compile();
pys.SetVariable("b", new DynamicValue());

long start = DateTime.Now.Ticks;
var res = compiled.Execute(pys);
long end = DateTime.Now.Ticks;

Console.WriteLine("...Finished. Sample data:");

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(res[i]);
}

Console.WriteLine("Took " + (end - start) / 10000 + "ms to run 1000000 times.");

Где DynamicValue - это класс, который возвращает случайные числа из предварительно созданного массива (сеяние и сборка во время выполнения).

Когда я создаю класс DLR для того же, я получаю гораздо более высокую производительность (~ 10 000 000 вычислений в секунду).Класс выглядит следующим образом:

class DynamicCalc : IDynamicMetaObjectProvider
{
    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)
    {
        return new DynamicCalcMetaObject(parameter, this);
    }

    private class DynamicCalcMetaObject : DynamicMetaObject
    {
        internal DynamicCalcMetaObject(Expression parameter, DynamicCalc value) : base(parameter, BindingRestrictions.Empty, value) { }

        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            Expression Add = Expression.Convert(Expression.Add(args[0].Expression, args[1].Expression), typeof(System.Object));
            DynamicMetaObject methodInfo = new DynamicMetaObject(Expression.Block(Add), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
            return methodInfo;
        }
    }
}

и вызывается / тестируется таким же образом, выполняя следующие действия:

dynamic obj = new DynamicCalc();
long t1 = DateTime.Now.Ticks;
for (int i = 0; i < 10000000; i++)
{
    results[i] = obj.Add(ar1[i], ar2[i]);
}
long t2 = DateTime.Now.Ticks;

Где ar1 и ar2 - предварительно созданные массивы, заполненные во время выполненияслучайных чисел.

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

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

Мой пример лучше всего использует движок IronPython?Можно ли из этого добиться значительно лучшей производительности?

(Edit) То же, что и в первом примере, но с циклом в C #, установкой переменных и вызовом функции python:

ScriptSource src = py.CreateScriptSourceFromString(@"b + 1");

CompiledCode compiled = src.Compile();

double[] res = new double[1000000];

for(int i=0; i<1000000; i++)
{
    pys.SetVariable("b", args1[i]);
    res[i] = compiled.Execute(pys);
}

гдеpys - это ScriptScope от py, а args1 - это предварительно созданный массив случайных двойников.Этот пример выполняется медленнее, чем выполнение цикла в коде Python и передача целых массивов.

Ответы [ 2 ]

2 голосов
/ 02 февраля 2011

Комментарий Делнана приводит вас к некоторым проблемам здесь.Но я просто уточню, какие здесь различия.В версии C # вы отключили значительное количество динамических вызовов, которые есть в версии Python.Для начала ваш цикл набирается как int, и звучит так, что ar1 и ar2 - строго типизированные массивы.Таким образом, в версии C # единственными динамическими операциями, которые у вас есть, являются вызов obj.Add (который является 1 операцией в C #) и, возможно, присвоение результатов, если оно не типизировано для объекта, что кажется маловероятным.Также обратите внимание, что весь этот код свободен от блокировки.

В версии Python у вас сначала есть распределение списка - это также происходит во время вашего таймера, где, как в C #, это не выглядит так, как есть.Тогда у вас есть динамический вызов диапазона, к счастью, это происходит только один раз.Но это снова создает гигантский список в памяти - предложение Делнана о xrange здесь является улучшением.Затем у вас есть счетчик циклов i, который упаковывается в объект для каждой итерации цикла.Затем у вас есть вызов b.GetValue (), который на самом деле представляет собой 2 динамических invocatiosn - сначала член get для получения метода «GetValue», а затем вызов этого объекта связанного метода.Это снова создает один новый объект для каждой итерации цикла.Затем у вас есть результат b.GetValue (), который может быть еще одним значением, которое упаковывается на каждой итерации.Затем вы добавляете 1 к этому результату, и на каждой итерации вы выполняете еще одну операцию упаковки.Наконец, вы сохраняете это в своем списке, что является еще одной динамической операцией - я думаю, что эту последнюю операцию необходимо заблокировать, чтобы список оставался непротиворечивым (опять же, предложение Делнана об использовании понимания списка улучшает это).

Так что вПодводя итоги цикла, мы имеем:

                            C#       IronPython
Dynamic Operations           1           4
Allocations                  1           4
Locks Acquired               0           1

Таким образом, в основном динамическое поведение Python имеет цену против C #.Если вам нужно лучшее из обоих миров, вы можете попытаться сбалансировать то, что вы делаете в C #, и то, что вы делаете в Python.Например, вы можете написать цикл в C # и вызвать его делегатом, который является функцией Python (вы можете сделать scope.GetVariable>, чтобы вывести функцию из области видимости в качестве делегата).Вы также можете рассмотреть вопрос о выделении массива .NET для результатов, если вам действительно нужно получить каждый последний бит производительности, так как это может уменьшить рабочий набор и копирование GC, не сохраняя кучу коробочных значений.

Для выполненияделегат, которого пользователь мог бы написать:

def computeValue(value):
    return value + 1

Затем в коде C # вы сделаете:

CompiledCode compiled = src.Compile();
compiled.Execute(pys);
var computer = pys.GetVariable<Func<object,object>>("computeValue");

Теперь вы можете сделать:

for (int i = 0; i < 10000000; i++)
{
    results[i] = computer(i);
}
0 голосов
/ 26 ноября 2017

Если вас беспокоит скорость вычислений, лучше ли посмотреть на lowlevel спецификацию вычислений? Python и C # - языки высокого уровня, и время его реализации может потратить много времени на работу под прикрытием.

Посмотрите на эту библиотеку оболочки LLVM: http://www.llvmpy.org

  • Установите его, используя: pip install llvmpy ply
  • или в Debian Linux: apt install python-llvmpy python-ply

Вам все еще нужно написать какой-нибудь крошечный компилятор (вы можете использовать PLY-библиотеку ) и связать его с вызовами JL LLVM (см. LLVM Execution Engine), но этот подход может быть более эффективным (генерирующий код намного ближе к реальному коду ЦП) и мультиплатформенность по сравнению с .NET jail.

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

Также смотрите здесь: http://gmarkall.github.io/tutorials/llvm-cauldron-2016

PS: Если вам это интересно, я могу помочь вам с компилятором, параллельно добавляя руководство к моему проекту. Но это не будет сложным делом, эта тема для меня тоже нова.

...