パフォーマンスのための動的コンパイル
-
20-09-2019 - |
質問
動的コード生成を使用してパフォーマンスを向上させる方法については考えていますが、この問題に対処する最善の方法はどれかわかりません。
クラスがあるとします
class Calculator
{
int Value1;
int Value2;
//..........
int ValueN;
void DoCalc()
{
if (Value1 > 0)
{
DoValue1RelatedStuff();
}
if (Value2 > 0)
{
DoValue2RelatedStuff();
}
//....
//....
//....
if (ValueN > 0)
{
DoValueNRelatedStuff();
}
}
}
DoCalc メソッドは最下位レベルにあり、計算中に何度も呼び出されます。もう 1 つの重要な側面は、ValueN は最初にのみ設定され、計算中に変更されないことです。ValueN の多くは 0 であるため、DoCalc メソッドの if の多くは不要です。そのため、動的コード生成がパフォーマンスの向上に役立つことを期待していました。
たとえばメソッドを作成すると、
void DoCalc_Specific()
{
const Value1 = 0;
const Value2 = 0;
const ValueN = 1;
if (Value1 > 0)
{
DoValue1RelatedStuff();
}
if (Value2 > 0)
{
DoValue2RelatedStuff();
}
....
....
....
if (ValueN > 0)
{
DoValueNRelatedStuff();
}
}
C# コンパイラは、必要なものだけを保持するのに十分賢いので、最適化をオンにしてコンパイルします。そこで、実行時に ValueN の値に基づいてそのようなメソッドを作成し、計算中に生成されたメソッドを使用したいと思います。
そのために式ツリーを使用できると思いますが、式ツリーは単純なラムダ関数でのみ機能するため、if、while などは使用できません。関数本体内。したがって、この場合は、このメソッドを適切に変更する必要があります。
もう 1 つの方法は、必要なコードを文字列として作成し、動的にコンパイルすることです。しかし、既存の方法を採用して、それに応じて変更できれば、私にとってははるかに良いでしょう。
Reflection.Emit もありますが、維持するのが非常に難しいため、これにはこだわりません。
ところで。C# に限定されません。したがって、この種の問題に最適なプログラミング言語の提案を歓迎します。いくつかの理由から LISP を除きます。
重要な説明が 1 つあります。DoValue1ManyStuff() は、私のアルゴリズムではメソッド呼び出しではありません。これは数式に基づいた計算だけであり、非常に高速です。こう書けば良かった
if (Value1 > 0)
{
// Do Value1 Related Stuff
}
いくつかのパフォーマンス テストを実行したところ、2 つの if を使用して 1 つを無効にした場合、最適化された方法は冗長 if を使用した場合よりも約 2 倍高速であることがわかりました。
テストに使用したコードは次のとおりです。
public class Program
{
static void Main(string[] args)
{
int x = 0, y = 2;
var if_st = DateTime.Now.Ticks;
for (var i = 0; i < 10000000; i++)
{
WithIf(x, y);
}
var if_et = DateTime.Now.Ticks - if_st;
Console.WriteLine(if_et.ToString());
var noif_st = DateTime.Now.Ticks;
for (var i = 0; i < 10000000; i++)
{
Without(x, y);
}
var noif_et = DateTime.Now.Ticks - noif_st;
Console.WriteLine(noif_et.ToString());
Console.ReadLine();
}
static double WithIf(int x, int y)
{
var result = 0.0;
for (var i = 0; i < 100; i++)
{
if (x > 0)
{
result += x * 0.01;
}
if (y > 0)
{
result += y * 0.01;
}
}
return result;
}
static double Without(int x, int y)
{
var result = 0.0;
for (var i = 0; i < 100; i++)
{
result += y * 0.01;
}
return result;
}
}
解決
私は通常でも、このような最適化を考えていないでしょう。 DoValueXRelatedStuff()
はどのくらいの仕事をするのでしょうか?以上の10〜50プロセッサ・サイクル?はい?それはあなたが10%未満の実行時間を節約するために、非常に複雑なシステムを構築しようとしていること(これは私には非常に楽観的なようです)。これは、簡単に少ないし、1%にまで行くことができます。
他の最適化の余地はありませんか?より良いアルゴリズム?アンは、あなたは本当に(分岐予測が正しい場合)のみ、単一のプロセッサ・サイクルを取って、単一の枝を排除する必要がありますか?はい?あなたはアセンブラまたはその代わりに.NETを使用して、特定の何か他のものよりマシンでコードを書くことを考えるべきではないでしょうか。
あなたはN
のため、典型的な方法の複雑さ、および通常はtrueに評価表現の比率を与えてもらえますか?
他のヒント
あなたは、コードの最適化に実際にある場合 - あなたは何を行う前に - プロファイラを実行します!ボトルネックがあるとどの領域が最適化する価値がある場所それはあなたが表示されます。
また - 言語の選択は(LISPを除く)は限定されないならば、何もパフォーマンスの面でアセンブラを破っていないでしょう。)
私はアセンブラを使用して(あなたが持っているような)いくつかの内部関数を書き換えることにより、いくつかのパフォーマンスの魔法を達成覚えています。
何かをする前に、 実際に問題がありますか?
つまり気になるほど長く動作しますか?
その場合は、実際に何が時間がかかっているかを確認し、 あなたが推測するものではありません. これ これは、時間の経過を確認するために私が使用している、手早く、汚くて、非常に効果的な方法です。
さて、あなたは解釈とコンパイルについて話しています。解釈されたコードは通常、コンパイルされたコードよりも 1 ~ 2 桁遅くなります。その理由は、通訳者は次に何をすべきかを常に考えているためです。 そして忘れてしまう, 、コンパイルされたコード中 ただ知っている.
このような状況に陥った場合は、コンパイルされたコードの速度を得るために翻訳の代償を払うのが合理的かもしれません。