1

手動で指定した多数の計算を可能な限り高速に実行するWebサービスを作成するつもりであり、DLRの使用を検討してきました。

これが長い場合は申し訳ありませんが、お気軽にざっと目を通し、一般的な要点を理解してください。

計算を非常に簡単に指定できるため、IronPythonライブラリを使用しています。私の作品のラップトップは、次のことを行うと、毎秒約40万回の計算のパフォーマンスを提供します。

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クラスを作成すると、はるかに高いパフォーマンスが得られます(1秒あたり約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は事前に作成されたランダムなdoubleの配列です。この例は、Pythonコードでループを実行し、配列全体を渡すよりも実行速度が遅くなります。

4

2 に答える 2

2

デルナンのコメントは、ここでの問題のいくつかにあなたを導きます。ただし、ここでの違いについて具体的に説明します。C#バージョンでは、Pythonバージョンでの動的呼び出しを大幅に削減しました。手始めに、ループはintに入力され、ar1とar2は厳密に型指定された配列のように聞こえます。したがって、C#バージョンでは、obj.Addの呼び出し(C#では1つの操作)と、オブジェクトに入力されていない場合は結果への割り当てのみが動的操作になります。また、このコードはすべてロックフリーであることに注意してください。

Pythonバージョンでは、最初にリストを割り当てます。これもタイマー中に発生しているように見えますが、C#の場合とは異なります。次に、範囲への動的な呼び出しがあります。幸い、これは1回だけ発生します。しかし、それは再び記憶に巨大なリストを作成します-デルナンのxrangeの提案はここでの改善です。次に、ループの反復ごとにオブジェクトにボックス化されるループカウンターiがあります。次に、実際には2つの動的呼び出しであるb.GetValue()を呼び出します。最初にgetメンバーで「GetValue」メソッドを取得し、次にそのバインドされたメソッドオブジェクトを呼び出します。これにより、ループの反復ごとに1つの新しいオブジェクトが再び作成されます。次に、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);
}
于 2011-02-02T03:37:45.900 に答える
0

計算速度が気になる場合は、低レベルの計算仕様を検討する方がよいでしょうか。PythonとC#は高級言語であり、その実装ランタイムは覆面作業に多くの時間を費やす可能性があります。

このLLVMラッパーライブラリを見てください:http ://www.llvmpy.org

  • 以下を使用してインストールします。pip install llvmpy ply
  • またはDebianLinuxの場合:apt install python-llvmpy python-ply

まだいくつかの小さなコンパイラを作成し(PLYライブラリを使用できます)、それをLLVM JIT呼び出し(LLVM実行エンジンを参照)とバインドする必要がありますが、このアプローチはより効果的(実際のCPUコードにはるかに近い生成コード)であり、マルチプラットフォームです.NETjailと比較します。

LLVMは、多くのオプティマイザーステージモジュール、および大規模なユーザーと開発者のコ​​ミュニティを含む、最適化コンパイラインフラストラクチャを使用する準備ができています。

こちらもご覧ください:http://gmarkall.github.io/tutorials/llvm-cauldron-2016

PS:興味があれば、コンパイラーを手伝って、プロジェクトのマニュアルに並行して貢献することができます。しかし、それはジャンプスタートではありません。このテーマは私にとっても新しいものです。

于 2017-11-26T11:17:25.383 に答える