Вопрос

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

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

Я использовал библиотеку Ironpython, так как это делает расчеты очень простыми для указания. My Works Naptop дает показатели около 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 предварительно построены, во время выполнения массивы случайных чисел.

Таким образом, скорость отличная, но нелегко указать расчет. Я бы в основном смотрел на создание моего собственного Lexer & Parser, тогда как Ironpython имеет все, что мне нужно, уже там.

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

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

(Редактировать) То же, что и первый пример, но с циклом в 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-это сценарий от PY, а Args1-предварительно построенный массив случайных парных разрядов. Этот пример выполняется медленнее, чем запуск петли в коде Python и прохождение во всех массивах.

Это было полезно?

Решение

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

В версии Python у вас сначала есть распределение списка - это также кажется во время вашего таймера, где, как в C#, он не выглядит так. Тогда у вас есть динамический вызов в диапазоне, к счастью, это происходит только один раз. Но это снова создает гигантский список в памяти - предложение Delnan о XRange - это улучшение здесь. Тогда у вас есть счетчик петли I, который попадает в штучку на объект для каждой итерации через цикл. Затем у вас есть призыв к B.GetValue (), который на самом деле является 2 Dynamic 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);
}

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

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

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

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

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

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

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

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

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