DLR&性能

时间:2011-02-01 15:11:41

标签: python ironpython dynamic-language-runtime dynamicobject idynamicobject

我打算创建一个尽可能快地执行大量手动指定计算的Web服务,并且一直在探索使用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引擎?是否有可能从中获得更好的性能?

(编辑)与第一个例子相同,但是在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的ScriptScope,而args1是预先构建的随机双精度数组。此示例的执行速度比在Python代码中运行循环并传入整个数组要慢。

2 个答案:

答案 0 :(得分:2)

delnan的评论会引导您解决此处的一些问题。但我会详细说明这里的差异。在C#版本中,您已经删除了Python版本中的大量动态调用。对于初学者来说,你的循环是int类型,它听起来像ar1和ar2是强类型数组。因此,在C#版本中,您所拥有的唯一动态操作是调用obj.Add(在C#中是1个操作),如果没有输入到对象,则可能会对结果进行分配,这似乎不太可能。另请注意,所有这些代码都是无锁的。

在Python版本中,您首先要分配列表 - 这也似乎是在您的计时器中,而在C#中它看起来不像。然后你有动态调用范围,幸运的是只发生一次。但这又创造了一个巨大的记忆清单 - 德尔南对xrange的建议是一种改进。然后你有循环计数器i,它通过循环每次迭代都被装箱一个对象。然后你调用b.GetValue()实际上是2个动态invocatiosn - 首先是get成员获取“GetValue”方法,然后调用该绑定方法对象。这又为循环的每次迭代创建一个新对象。然后你得到b.GetValue()的结果,它可能是每次迭代时装箱的另一个值。然后你将1添加到该结果,并且每次迭代都有另一个装箱操作。最后你将它存储到你的列表中这是另一个动态操作 - 我认为这个最终操作需要锁定以确保列表保持一致(同样,delnan建议使用列表理解改进了这一点)。

总而言之,在循环中我们有:

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

因此,基本上Python的动态行为确实与C#相比有所成本。如果你想要两全其美,你可以尝试平衡你在C#中做的事情与你在Python中做的事情。例如,您可以在C#中编写循环并让它调用一个委托,这是一个Python函数(您可以使用scope.GetVariable&gt;以作为委托从作用域中获取函数)。您还可以考虑为结果分配.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);
}

答案 1 :(得分:0)

如果您关心计算速度,那么查看低级计算规范是否更好? Python和C#是高级语言,其实现运行时可能会花费大量时间进行秘密工作。

查看此LLVM包装器库:http://www.llvmpy.org

  • 使用以下内容进行安装:pip install llvmpy ply
  • 或在Debian Linux上:apt install python-llvmpy python-ply

您仍然需要编写一些小编译器(可以使用PLY library),并将其与LLVM JIT调用绑定(请参阅LLVM执行引擎),但这种方法可以更有效(生成的代码更接近实际CPU代码)和 multiplatform 与.NET jail相比。

LLVM已准备好使用优化编译器基础架构,包括许多优化器阶段模块,以及大用户和开发人员社区。

另见:http://gmarkall.github.io/tutorials/llvm-cauldron-2016

PS:如果您对此感兴趣,我可以帮助您使用编译器,并行地为我的项目手册做出贡献。但它不会是快速启动,这个主题对我来说也是新的。