文字列の比較に基づいて取得するコードパスを特定するか、型を調べるオプションがあるとしましょう。
どちらが速いですか、そしてなぜですか?
switch(childNode.Name)
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
if(childNode is Bob)
{
}
elseif(childNode is Jill)
{
}
else if(childNode is Marko)
{
}
更新:私がこれを尋ねる主な理由は、switchステートメントがケースと見なされるものについて奇妙だからです。たとえば、変数を使用することはできません。メインアセンブリに移動する定数のみを使用できます。ファンキーなことをしているため、この制限があると思いました。 (ある投稿者がコメントしたように)elseifにのみ翻訳する場合、なぜcaseステートメントで変数を使用できないのですか?
警告:最適化後です。このメソッドは、アプリの遅い部分でmany回呼び出されます。
Gregのプロファイル結果は、彼がカバーした正確なシナリオには適していますが、興味深いことに、比較対象の型の数、基になるデータの相対頻度やパターンなど、さまざまな要因を考慮すると、さまざまな方法の相対コストが劇的に変化します。
簡単な答えは、特定のシナリオでパフォーマンスの違いがどのようなものになるかを誰も教えてくれないということです。正確な答えを得るには、システムでさまざまな方法でパフォーマンスを測定する必要があります。
If/Elseチェーンは、少数の型の比較、または表示される型の大部分を占める少数の型を確実に予測できる場合に効果的なアプローチです。このアプローチの潜在的な問題は、型の数が増えると、実行する必要がある比較の数も増えることです。
次を実行した場合:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
正しいブロックに入る前に、前のif条件のそれぞれを評価する必要があります。一方
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
コードの正しいビットに1つの簡単なジャンプを実行します。
あなたの例でそれがより複雑になるのは、他の方法が少し複雑になる整数ではなく文字列のスイッチを使用することです。低レベルでは、整数値と同じ方法で文字列をオンに切り替えることはできないため、C#コンパイラはこれを機能させるための魔法をかけます。
Switchステートメントが「十分に小さい」場合(コンパイラーが自動的に最適と考えることをコンパイラーが行う場合)、ストリングを切り替えると、if/elseチェーンと同じコードが生成されます。
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
次と同じです:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
辞書内の項目のリストが「十分に大きく」なると、コンパイラはスイッチ内の文字列から整数インデックスにマップする内部辞書を自動的に作成し、そのインデックスに基づいてスイッチを作成します。
これは次のようになります(入力するのに煩わされるよりも多くのエントリを想像してください)
静的フィールドは、タイプDictionary<string, int>
のswitchステートメントを含むクラスに関連付けられ、マングルされた名前が指定された「隠された」場所で定義されます
//Make sure the dictionary is loaded
if(theDictionary == null) {
//This is simplified for clarity, the actual implementation is more complex
// in order to ensure thread safety
theDictionary = new Dictionary<string,int>();
theDictionary["Foo"] = 0;
theDictionary["Bar"] = 1;
}
int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
switch(switchIndex) {
case 0: DoFoo(); break;
case 1: DoBar(); break;
}
} else {
DoOther();
}
実行したばかりのいくつかの簡単なテストでは、If/Elseメソッドは、3つの異なるタイプ(タイプがランダムに分散されている)のスイッチの約3倍の速さです。 25タイプでは、スイッチはわずかなマージン(16%)で高速になりますが、50タイプでは、スイッチは2倍以上高速になります。
多数のタイプを切り替える場合は、3番目の方法をお勧めします。
private delegate void NodeHandler(ChildNode node);
static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();
private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();
ret[typeof(Bob).TypeHandle] = HandleBob;
ret[typeof(Jill).TypeHandle] = HandleJill;
ret[typeof(Marko).TypeHandle] = HandleMarko;
return ret;
}
void HandleChildNode(ChildNode node)
{
NodeHandler handler;
if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
{
handler(node);
}
else
{
//Unexpected type...
}
}
これはTed Elliotが提案したものと似ていますが、完全な型オブジェクトの代わりにランタイム型ハンドルを使用すると、リフレクションを介して型オブジェクトをロードするオーバーヘッドを回避できます。
私のマシンのいくつかの簡単なタイミングを以下に示します。
5,000,000個のデータ要素(mode = Random)と5タイプ Method Time%of Optimization If/Else 179.67 100.00 TypeHandleDictionary 321.33 178.85 TypeDictionary 377.67 210.20 Switch 492.67 274.21 5,000,000個のデータ要素(mode = Random)と10タイプ Method Time%of Optimization If/Else 271.33 100.00 TypeHandleDictionary 312.00 114.99 TypeDictionary 374.33 137.96 Switch 490.33 180.71 5,000,000個のデータ要素を使用した3回の反復のテスト(mode = Random)および15タイプ Method Time%of ideal TypeHandleDictionary 312.00 100.00 If/Else 369.00 118.27 TypeDictionary 371.67 119.12 スイッチ491.67 157.59 5,000,000 datで3回の反復をテストするa要素(mode = Random)および20タイプ Method Time%of Optimum TypeHandleDictionary 335.33 100.00 TypeDictionary 373.00 111.23 If/Else 462.67 137.97 スイッチ490.33 146.22 5,000,000個のデータ要素(mode = Random)と25種類の3回の反復のテスト Method Time%of Optimization TypeHandleDictionary 319.33 100.00 TypeDictionary 371.00 116.18 Switch 483.00 151.25 If/Else 562.00 175.99 5,000,000個のデータ要素(mode = Random)と50タイプでの3回の反復のテスト
少なくとも私のマシンでは、タイプハンドルディクショナリアプローチは、メソッドへの入力として使用されるタイプの分布がランダムである場合、15を超えるさまざまなタイプに対して他のすべてを凌beatします。
一方、入力が完全にif/elseチェーンで最初にチェックされるタイプで構成されている場合、そのメソッドはmuchより高速です:
5,000,000個のデータ要素(mode = UniformFirst)と50タイプでの3回の反復のテスト Method Time%of Optimization If/Else 39.00 100.00 TypeHandleDictionary 317.33 813.68 TypeDictionary 396.00 1,015.38 Switch 403.00 1,033.33
逆に、入力が常にif/elseチェーンの最後のものである場合、逆の効果があります。
5,000,000個のデータ要素(mode = UniformLast)と50タイプでの3回の反復のテスト Method Time%of Optimization TypeHandleDictionary 317.67 100.00 Switch 354.33 111.54 TypeDictionary 377.67 118.89 If/Else 1,907.67 600.52
入力についていくつかの仮定を立てることができる場合、最も一般的ないくつかのタイプに対してif/elseチェックを実行し、それらが失敗した場合は辞書駆動のアプローチにフォールバックするハイブリッドアプローチから最高のパフォーマンスを得ることができます。
クイックテストアプリケーションを実装し、ANTS 4でプロファイルしました。
仕様:32ビットWindows XPの.Net 3.5 sp1、リリースモードでビルドされたコード。
300万件のテスト:
さらに、switchステートメントの結果から、(当然のことながら)名前が長いほど時間がかかることがわかります。
100万回のテスト
少なくとも私が作成したシナリオでは、「If Else」の方が速いようです。
class Program
{
static void Main( string[] args )
{
Bob bob = new Bob();
Jill jill = new Jill();
Marko marko = new Marko();
for( int i = 0; i < 1000000; i++ )
{
Test( bob );
Test( jill );
Test( marko );
}
}
public static void Test( ChildNode childNode )
{
TestSwitch( childNode );
TestIfElse( childNode );
}
private static void TestIfElse( ChildNode childNode )
{
if( childNode is Bob ){}
else if( childNode is Jill ){}
else if( childNode is Marko ){}
}
private static void TestSwitch( ChildNode childNode )
{
switch( childNode.Name )
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
}
}
class ChildNode { public string Name { get; set; } }
class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}
class Jill : ChildNode{public Jill(){this.Name = "Jill";}}
class Marko : ChildNode{public Marko(){this.Name = "Marko";}}
まず、リンゴとオレンジを比較しています。まず、タイプの切り替えと文字列の切り替えを比較し、次にタイプの場合と文字列の切り替えを比較してから、勝者を比較する必要があります。
第二に、これはOOのために設計されたものです。OOをサポートする言語では、(あらゆる種類の)タイプの切り替えはコードのにおいであり、設計が不十分であることを示します。抽象メソッドまたは仮想メソッド(または言語に応じて同様の構成要素)を使用して共通ベースから派生する
例えば。
class Node
{
public virtual void Action()
{
// Perform default action
}
}
class Bob : Node
{
public override void Action()
{
// Perform action for Bill
}
}
class Jill : Node
{
public override void Action()
{
// Perform action for Jill
}
}
次に、switchステートメントを実行する代わりに、childNode.Action()を呼び出すだけです。
Switchステートメントは、if-else-ifラダーよりも実行が高速です。これは、switchステートメントを最適化するコンパイラーの機能によるものです。 if-else-ifラダーの場合、コードはプログラマーが決定した順序で各ifステートメントを処理する必要があります。ただし、switchステートメント内の各ケースは以前のケースに依存しないため、コンパイラーは、最速の実行を提供するような方法でテストを並べ替えることができます。
クラスを作成したら、switchまたはelseifの代わりにStrategyデザインパターンを使用することをお勧めします。
各オブジェクトに対して列挙を使用してみてください。列挙をすばやく簡単に有効にできます。
すでにこれを書いていて、パフォーマンスの問題があるとわかっていない限り、どちらが速いかは気にしません。より読みやすいものを使用してください。 「時期尚早の最適化がすべての悪の根源である」ことを忘れないでください。 -ドナルドクヌース
SWITCHコンストラクトは元々整数データ用でした。目的は、引数をポインターのテーブルである「ディスパッチテーブル」へのインデックスとして直接使用することでした。そのため、1つのテストがあり、一連のテストではなく、関連するコードを直接起動します。
ここでの難点は、その使用が一般にインデックスとして使用できない「文字列」型に一般化されており、SWITCH構造のすべての利点が失われていることです。
速度が目的の場合、問題はコードではなくデータ構造です。 「名前」スペースが表示と同じくらい簡単な場合は、整数値にコード化して(データの作成時など)、この整数を「アプリの遅い部分で何度も」使用することをお勧めします。
切り替えるタイプがプリミティブな.NETタイプの場合、Type.GetTypeCode(Type)を使用できますが、カスタムタイプの場合は、すべてTypeCode.Objectとして返されます。
デリゲートまたはハンドラークラスを持つディクショナリも同様に機能する場合があります。
Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;
handlers[childNode.GetType()](childNode);
/// ...
private void HandleBob(Node childNode) {
// code to handle Bob
}
速度の違いを強調するために、ソリューションを表示する小さなコンソールを作成しました。証明書のバージョンは実行時に遅くなるため、別の文字列ハッシュアルゴリズムを使用しました。重複が発生する可能性は低いため、switchステートメントが失敗します(今まで発生しませんでした)。私の独自のハッシュ拡張メソッドは、以下のコードに含まれています。
特に重要なコードを使用する場合は、いつでも695ティック以上29ティックかかります。
特定のデータベースの一連の文字列を使用して、コードで使用するための特定のファイルに定数を作成する小さなアプリケーションを作成できます。値が追加された場合は、バッチを再実行し、定数を生成して取得しますソリューション。
public static class StringExtention
{
public static long ToUniqueHash(this string text)
{
long value = 0;
var array = text.ToCharArray();
unchecked
{
for (int i = 0; i < array.Length; i++)
{
value = (value * 397) ^ array[i].GetHashCode();
value = (value * 397) ^ i;
}
return value;
}
}
}
public class AccountTypes
{
static void Main()
{
var sb = new StringBuilder();
sb.AppendLine($"const long ACCOUNT_TYPE = {"AccountType".ToUniqueHash()};");
sb.AppendLine($"const long NET_LIQUIDATION = {"NetLiquidation".ToUniqueHash()};");
sb.AppendLine($"const long TOTAL_CASH_VALUE = {"TotalCashValue".ToUniqueHash()};");
sb.AppendLine($"const long SETTLED_CASH = {"SettledCash".ToUniqueHash()};");
sb.AppendLine($"const long ACCRUED_CASH = {"AccruedCash".ToUniqueHash()};");
sb.AppendLine($"const long BUYING_POWER = {"BuyingPower".ToUniqueHash()};");
sb.AppendLine($"const long EQUITY_WITH_LOAN_VALUE = {"EquityWithLoanValue".ToUniqueHash()};");
sb.AppendLine($"const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = {"PreviousEquityWithLoanValue".ToUniqueHash()};");
sb.AppendLine($"const long GROSS_POSITION_VALUE ={ "GrossPositionValue".ToUniqueHash()};");
sb.AppendLine($"const long REQT_EQUITY = {"ReqTEquity".ToUniqueHash()};");
sb.AppendLine($"const long REQT_MARGIN = {"ReqTMargin".ToUniqueHash()};");
sb.AppendLine($"const long SPECIAL_MEMORANDUM_ACCOUNT = {"SMA".ToUniqueHash()};");
sb.AppendLine($"const long INIT_MARGIN_REQ = { "InitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long MAINT_MARGIN_REQ = {"MaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long AVAILABLE_FUNDS = {"AvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long EXCESS_LIQUIDITY = {"ExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long CUSHION = {"Cushion".ToUniqueHash()};");
sb.AppendLine($"const long FULL_INIT_MARGIN_REQ = {"FullInitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long FULL_MAINTMARGIN_REQ ={ "FullMaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long FULL_AVAILABLE_FUNDS = {"FullAvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long FULL_EXCESS_LIQUIDITY ={ "FullExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_INIT_MARGIN_REQ = {"LookAheadInitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_MAINT_MARGIN_REQ = {"LookAheadMaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_AVAILABLE_FUNDS = {"LookAheadAvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_EXCESS_LIQUIDITY = {"LookAheadExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long HIGHEST_SEVERITY = {"HighestSeverity".ToUniqueHash()};");
sb.AppendLine($"const long DAY_TRADES_REMAINING = {"DayTradesRemaining".ToUniqueHash()};");
sb.AppendLine($"const long LEVERAGE = {"Leverage".ToUniqueHash()};");
Console.WriteLine(sb.ToString());
Test();
}
public static void Test()
{
//generated constant values
const long ACCOUNT_TYPE = -3012481629590703298;
const long NET_LIQUIDATION = 5886477638280951639;
const long TOTAL_CASH_VALUE = 2715174589598334721;
const long SETTLED_CASH = 9013818865418133625;
const long ACCRUED_CASH = -1095823472425902515;
const long BUYING_POWER = -4447052054809609098;
const long EQUITY_WITH_LOAN_VALUE = -4088154623329785565;
const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = 6224054330592996694;
const long GROSS_POSITION_VALUE = -7316842993788269735;
const long REQT_EQUITY = -7457439202928979430;
const long REQT_MARGIN = -7525806483981945115;
const long SPECIAL_MEMORANDUM_ACCOUNT = -1696406879233404584;
const long INIT_MARGIN_REQ = 4495254338330797326;
const long MAINT_MARGIN_REQ = 3923858659879350034;
const long AVAILABLE_FUNDS = 2736927433442081110;
const long EXCESS_LIQUIDITY = 5975045739561521360;
const long CUSHION = 5079153439662500166;
const long FULL_INIT_MARGIN_REQ = -6446443340724968443;
const long FULL_MAINTMARGIN_REQ = -8084126626285123011;
const long FULL_AVAILABLE_FUNDS = 1594040062751632873;
const long FULL_EXCESS_LIQUIDITY = -2360941491690082189;
const long LOOK_AHEAD_INIT_MARGIN_REQ = 5230305572167766821;
const long LOOK_AHEAD_MAINT_MARGIN_REQ = 4895875570930256738;
const long LOOK_AHEAD_AVAILABLE_FUNDS = -7687608210548571554;
const long LOOK_AHEAD_EXCESS_LIQUIDITY = -4299898188451362207;
const long HIGHEST_SEVERITY = 5831097798646393988;
const long DAY_TRADES_REMAINING = 3899479916235857560;
const long LEVERAGE = 1018053116254258495;
bool found = false;
var sValues = new string[] {
"AccountType"
,"NetLiquidation"
,"TotalCashValue"
,"SettledCash"
,"AccruedCash"
,"BuyingPower"
,"EquityWithLoanValue"
,"PreviousEquityWithLoanValue"
,"GrossPositionValue"
,"ReqTEquity"
,"ReqTMargin"
,"SMA"
,"InitMarginReq"
,"MaintMarginReq"
,"AvailableFunds"
,"ExcessLiquidity"
,"Cushion"
,"FullInitMarginReq"
,"FullMaintMarginReq"
,"FullAvailableFunds"
,"FullExcessLiquidity"
,"LookAheadInitMarginReq"
,"LookAheadMaintMarginReq"
,"LookAheadAvailableFunds"
,"LookAheadExcessLiquidity"
,"HighestSeverity"
,"DayTradesRemaining"
,"Leverage"
};
long t1, t2;
var sw = System.Diagnostics.Stopwatch.StartNew();
foreach (var name in sValues)
{
switch (name)
{
case "AccountType": found = true; break;
case "NetLiquidation": found = true; break;
case "TotalCashValue": found = true; break;
case "SettledCash": found = true; break;
case "AccruedCash": found = true; break;
case "BuyingPower": found = true; break;
case "EquityWithLoanValue": found = true; break;
case "PreviousEquityWithLoanValue": found = true; break;
case "GrossPositionValue": found = true; break;
case "ReqTEquity": found = true; break;
case "ReqTMargin": found = true; break;
case "SMA": found = true; break;
case "InitMarginReq": found = true; break;
case "MaintMarginReq": found = true; break;
case "AvailableFunds": found = true; break;
case "ExcessLiquidity": found = true; break;
case "Cushion": found = true; break;
case "FullInitMarginReq": found = true; break;
case "FullMaintMarginReq": found = true; break;
case "FullAvailableFunds": found = true; break;
case "FullExcessLiquidity": found = true; break;
case "LookAheadInitMarginReq": found = true; break;
case "LookAheadMaintMarginReq": found = true; break;
case "LookAheadAvailableFunds": found = true; break;
case "LookAheadExcessLiquidity": found = true; break;
case "HighestSeverity": found = true; break;
case "DayTradesRemaining": found = true; break;
case "Leverage": found = true; break;
default: found = false; break;
}
if (!found)
throw new NotImplementedException();
}
t1 = sw.ElapsedTicks;
sw.Restart();
foreach (var name in sValues)
{
switch (name.ToUniqueHash())
{
case ACCOUNT_TYPE:
found = true;
break;
case NET_LIQUIDATION:
found = true;
break;
case TOTAL_CASH_VALUE:
found = true;
break;
case SETTLED_CASH:
found = true;
break;
case ACCRUED_CASH:
found = true;
break;
case BUYING_POWER:
found = true;
break;
case EQUITY_WITH_LOAN_VALUE:
found = true;
break;
case PREVIOUS_EQUITY_WITH_LOAN_VALUE:
found = true;
break;
case GROSS_POSITION_VALUE:
found = true;
break;
case REQT_EQUITY:
found = true;
break;
case REQT_MARGIN:
found = true;
break;
case SPECIAL_MEMORANDUM_ACCOUNT:
found = true;
break;
case INIT_MARGIN_REQ:
found = true;
break;
case MAINT_MARGIN_REQ:
found = true;
break;
case AVAILABLE_FUNDS:
found = true;
break;
case EXCESS_LIQUIDITY:
found = true;
break;
case CUSHION:
found = true;
break;
case FULL_INIT_MARGIN_REQ:
found = true;
break;
case FULL_MAINTMARGIN_REQ:
found = true;
break;
case FULL_AVAILABLE_FUNDS:
found = true;
break;
case FULL_EXCESS_LIQUIDITY:
found = true;
break;
case LOOK_AHEAD_INIT_MARGIN_REQ:
found = true;
break;
case LOOK_AHEAD_MAINT_MARGIN_REQ:
found = true;
break;
case LOOK_AHEAD_AVAILABLE_FUNDS:
found = true;
break;
case LOOK_AHEAD_EXCESS_LIQUIDITY:
found = true;
break;
case HIGHEST_SEVERITY:
found = true;
break;
case DAY_TRADES_REMAINING:
found = true;
break;
case LEVERAGE:
found = true;
break;
default:
found = false;
break;
}
if (!found)
throw new NotImplementedException();
}
t2 = sw.ElapsedTicks;
sw.Stop();
Console.WriteLine($"String switch:{t1:N0} long switch:{t2:N0}");
var faster = (t1 > t2) ? "Slower" : "faster";
Console.WriteLine($"String switch: is {faster} than long switch: by {Math.Abs(t1-t2)} Ticks");
Console.ReadLine();
}
If/else分岐はswitchステートメントよりも速いことをいくつかの参考書で読んだことを思い出します。ただし、Blackwaspに関する少しの調査では、switchステートメントの方が実際に高速であることが示されています: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
実際には、典型的な3対10(またはそれ以上)のステートメントを比較している場合、どちらか一方を使用した場合の実際のパフォーマンスの向上は真剣に疑われます。
クリスが既に言ったように、読みやすくするために行きます: 文字列をオンにするか、タイプをelseifにするのがより速いですか?
Switch()は、else ifのセットと同等のコードにコンパイルされます。文字列の比較は、型の比較よりもはるかに遅くなります。
ここでの主なパフォーマンスの問題は、switchブロックで文字列を比較することと、if-elseブロックで型をチェックすることだと思います...これら2つは同じではないので、 「ジャガイモとバナナの比較」です。
私はこれを比較することから始めます:
switch(childNode.Name)
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}
ポリモーフィズムを採用するのが適切な設計になるのがどれだけ速いかはわかりません。
interface INode
{
void Action;
}
class Bob : INode
{
public void Action
{
}
}
class Jill : INode
{
public void Action
{
}
}
class Marko : INode
{
public void Action
{
}
}
//Your function:
void Do(INode childNode)
{
childNode.Action();
}
Switchステートメントの動作を確認すると、より効果的です。あなたの関数がそのタイプのアクションに関するものではない場合、各タイプで列挙型を定義できるかもしれません。
enum NodeType { Bob, Jill, Marko, Default }
interface INode
{
NodeType Node { get; };
}
class Bob : INode
{
public NodeType Node { get { return NodeType.Bob; } }
}
class Jill : INode
{
public NodeType Node { get { return NodeType.Jill; } }
}
class Marko : INode
{
public NodeType Node { get { return NodeType.Marko; } }
}
//Your function:
void Do(INode childNode)
{
switch(childNode.Node)
{
case Bob:
break;
case Jill:
break;
case Marko:
break;
Default:
throw new ArgumentException();
}
}
これは、問題の両方のアプローチよりも高速でなければならないと思います。抽象クラスのルートを試すこともできます ナノ秒が重要な場合 。
私はここで答えのリストを読んでいて、共有することを望んでいました このベンチマークテストswitch
構文とif-else
および3進数?
演算子。
私が気に入っているのは その投稿 は、単一の左の構成要素(たとえば、if-else
)ただし、ダブルおよびトリプルレベルの構成(例:if-else-if-else
)。
結果によると、if-else
コンストラクトは8/9テストケースで最速でした。 switch
コンストラクトは、5/9テストケースで最速に結び付けられています。
したがって、速度を探している場合はif-else
が最も速い方法のようです。
プロファイラーはあなたの友達です。ほとんどの場合、当て推量は時間の無駄です。ところで、JetBrains ' dotTrace プロファイラーで良い経験をしました。
基本的にスイッチオン文字列はif-else-ifラダーにコンパイルされます。簡単なものを逆コンパイルしてみてください。いずれにせよ、文字列の同等性のテストはインターンされており、必要なのは参照チェックだけなので、より安価である必要があります。保守性の観点から意味のあることを行います。文字列をコンパイルしている場合は、文字列を切り替えます。タイプに基づいて選択する場合は、タイプラダーがより適切です。
文字列の比較は常にランタイム環境に完全に依存します(文字列が静的に割り当てられている場合を除き、文字列を相互に比較する必要性は議論の余地があります)。ただし、型の比較は動的または静的バインディングを介して行うことができ、どちらの方法でも、文字列内の個々の文字を比較するよりもランタイム環境にとって効率的です。
確かにStringのスイッチは、型の比較よりも遅い(およびスイッチ/ケースに使用される一般的な整数の比較よりもはるかに遅い)String比較(ケースごとに1つ)にコンパイルされますか?
私はちょっと違うことをしています。スイッチを入れる文字列は定数になるので、コンパイル時に値を予測できます。
あなたの場合、私はハッシュ値を使用します、これはintスイッチです、2つのオプションがあり、コンパイル時定数を使用するか、実行時に計算します。
//somewhere in your code
static long _bob = "Bob".GetUniqueHashCode();
static long _jill = "Jill".GetUniqueHashCode();
static long _marko = "Marko".GeUniquetHashCode();
void MyMethod()
{
...
if(childNode.Tag==0)
childNode.Tag= childNode.Name.GetUniquetHashCode()
switch(childNode.Tag)
{
case _bob :
break;
case _jill :
break;
case _marko :
break;
}
}
GetUniquetHashCodeの拡張メソッドは次のようになります。
public static class StringExtentions
{
/// <summary>
/// Return unique Int64 value for input string
/// </summary>
/// <param name="strText"></param>
/// <returns></returns>
public static Int64 GetUniquetHashCode(this string strText)
{
Int64 hashCode = 0;
if (!string.IsNullOrEmpty(strText))
{
//Unicode Encode Covering all character-set
byte[] byteContents = Encoding.Unicode.GetBytes(strText);
System.Security.Cryptography.SHA256 hash = new System.Security.Cryptography.SHA256CryptoServiceProvider();
byte[] hashText = hash.ComputeHash(byteContents);
//32Byte hashText separate
//hashCodeStart = 0~7 8Byte
//hashCodeMedium = 8~23 8Byte
//hashCodeEnd = 24~31 8Byte
//and Fold
Int64 hashCodeStart = BitConverter.ToInt64(hashText, 0);
Int64 hashCodeMedium = BitConverter.ToInt64(hashText, 8);
Int64 hashCodeEnd = BitConverter.ToInt64(hashText, 24);
hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
}
return (hashCode);
}
}
このコードのソースが公開されました here 暗号化の使用は遅いことに注意してください。通常、アプリケーションの起動時にサポートされている文字列をウォームアップします。インスタンス関連ではありません。ノードオブジェクトのタグ値を設定します。任意のプロパティを使用するか追加することができます。これらが実際のテキストと同期していることを確認してください。
私は低遅延システムで作業しており、すべてのコードはcommand:value、command:value ....の文字列として送られます。
現在、コマンドはすべて64ビット整数値として知られているため、このように切り替えるとCPU時間を節約できます。
スイッチに関する問題の1つは、 "Bob"などの文字列の使用です。これにより、コンパイルされたコードに多くのサイクルと行が発生します。生成されるILは、文字列を宣言し、「Bob」に設定してから比較で使用する必要があります。そのため、IFステートメントはより高速に実行されます。
PS。タイプをオンにできないため、Aeonの例は機能しません。 (いいえ、正確な理由はわかりませんが、試してみましたが、動作しません。変数の型に関係しています)
これをテストする場合は、別のアプリケーションをビルドし、上記で説明したことを行う2つの簡単なメソッドをビルドし、Ildasm.exeなどを使用してILを確認します。 IFステートメントMethodのILの行がはるかに少ないことに気付くでしょう。
IldasmにはVisualStudioが付属しています...
ILDASMページ- http://msdn.Microsoft.com/en-us/library/f7dy01k1(VS.80).aspx
ILDASMチュートリアル- http://msdn.Microsoft.com/en-us/library/aa309387(VS.71).aspx
3つの考え:
1)オブジェクトのタイプに基づいて異なることを行う場合、その動作をそれらのクラスに移動することは理にかなっているかもしれません。次に、switchまたはif-elseの代わりに、childNode.DoSomething()を呼び出すだけです。
2)型の比較は、文字列の比較よりもはるかに高速です。
3)if-else設計では、テストの並べ替えを利用できる場合があります。 「ジル」オブジェクトがそこを通過するオブジェクトの90%を構成する場合、最初にそれらをテストします。