具体的には、一連のif
...else if
ステートメントがあり、各ステートメントがtrue
に評価される相対的な確率を事前に知っている場合、それらを確率の順に並べ替えることはどの程度の違いがありますか。例えば、私はこれを好むべきです:
if (highly_likely)
//do something
else if (somewhat_likely)
//do something
else if (unlikely)
//do something
これに?
if (unlikely)
//do something
else if (somewhat_likely)
//do something
else if (highly_likely)
//do something
ソートされたバージョンのほうが速いことは明らかですが、読みやすさや副作用のために、それらを最適ではない順序で並べたいと思うかもしれません。実際にコードを実行するまで、CPUが分岐予測をどの程度うまく処理できるかを見分けるのも難しいです。
だから、これを試す過程で、私は特定のケースについて私自身の質問に答えることになりました、しかし私は他の意見/洞察を同様に聞きたいです。
重要:この質問では、if
ステートメントは、プログラムの動作に他の影響を与えることなく任意に並べ替えることができると仮定しています。私の答えでは、3つの条件付きテストは相互に排他的で副作用はありません。確かに、ステートメントを望ましい順序で実行するために特定の順序で評価する必要がある場合、効率性の問題は重要ではありません。
一般的な規則として、すべてではないにしてもほとんどのIntel CPUは前方分岐がそれらに最初に会ったときにとられないと仮定します。 Godboltの作品 を参照してください。
その後、分岐は分岐予測キャッシュに入り、過去の振る舞いは将来の分岐予測を通知するために使用されます。
そのため、タイトなループでは、並べ替えミスの影響は比較的小さくなります。分岐予測子は、どの分岐セットが最も可能性が高いかを学習します。ループ内に自明ではない作業量がある場合は、わずかな違いでも足りません。
一般的なコードでは、ほとんどのコンパイラはデフォルトで(別の理由で欠けています)、生成されたマシンコードをおおよそあなたのコードの中で並べたのとほぼ同じ順序で並べます。したがって、if文は失敗したときに前方分岐になります。
そのため、「最初の出会い」から最良の分岐予測を得るために、可能性が低い順に分岐を並べ替える必要があります。
一連の条件にわたって何度も緊密にループして些細な作業を実行するマイクロベンチマークは、命令数などのごくわずかな影響によって支配されようとしていますが、相対的な分岐予測の問題はほとんどありません。したがって、この場合、経験則は信頼できないので、でプロファイルを実行する必要があります。
それに加えて、ベクトル化や他の多くの最適化は小さなタイトなループに適用されます。
そのため、一般的なコードでは、最も可能性の高いコードをif
ブロック内に配置すると、キャッシュされていない分岐予測ミスが最小限に抑えられます。タイトなループでは、開始するための一般的なルールに従ってください、あなたがより多くを知る必要がある場合はあなたがプロファイルする以外に選択肢はほとんどありません。
当然のことながら、いくつかのテストが他のテストよりはるかに安価であれば、これはすべて問題になります。
2つの異なるif
...else if
ブロックの実行タイミングを計るために、次のテストを作成しました。一方は確率の順にソートされ、もう一方は逆の順序にソートされています。
#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <functional>
using namespace std;
int main()
{
long long sortedTime = 0;
long long reverseTime = 0;
for (int n = 0; n != 500; ++n)
{
//Generate a vector of 5000 random integers from 1 to 100
random_device rnd_device;
mt19937 rnd_engine(rnd_device());
uniform_int_distribution<int> rnd_dist(1, 100);
auto gen = std::bind(rnd_dist, rnd_engine);
vector<int> Rand_vec(5000);
generate(begin(Rand_vec), end(Rand_vec), gen);
volatile int nLow, nMid, nHigh;
chrono::time_point<chrono::high_resolution_clock> start, end;
//Sort the conditional statements in order of increasing likelyhood
nLow = nMid = nHigh = 0;
start = chrono::high_resolution_clock::now();
for (int& i : Rand_vec) {
if (i >= 95) ++nHigh; //Least likely branch
else if (i < 20) ++nLow;
else if (i >= 20 && i < 95) ++nMid; //Most likely branch
}
end = chrono::high_resolution_clock::now();
reverseTime += chrono::duration_cast<chrono::nanoseconds>(end-start).count();
//Sort the conditional statements in order of decreasing likelyhood
nLow = nMid = nHigh = 0;
start = chrono::high_resolution_clock::now();
for (int& i : Rand_vec) {
if (i >= 20 && i < 95) ++nMid; //Most likely branch
else if (i < 20) ++nLow;
else if (i >= 95) ++nHigh; //Least likely branch
}
end = chrono::high_resolution_clock::now();
sortedTime += chrono::duration_cast<chrono::nanoseconds>(end-start).count();
}
cout << "Percentage difference: " << 100 * (double(reverseTime) - double(sortedTime)) / double(sortedTime) << endl << endl;
}
MSVC2017を/ O2と組み合わせて使用すると、ソートされたバージョンはソートされていないバージョンよりも一貫して約28%高速であることがわかります。 luk32のコメントによると、私は2つのテストの順序も入れ替えましたが、それは目立った違いを生んでいます(22%対28%)。コードは、Intel Xeon E5-2697 v2上のWindows 7で実行されました。これは、もちろん、非常に問題に特有のものであり、決定的な答えとして解釈されるべきではありません。
ターゲットシステムが影響を受けていると本当に確信しているのでなければ、できません。デフォルトでは読みやすくなっています。
あなたの結果には疑いがあります。私はあなたの例を少し修正したので、実行を逆にするほうが簡単です。 Ideone 逆順が速いことを一貫して示していますが、大したことはありません。特定のランではこれさえ時々ひっくり返った。結果は決定的ではないと思います。 colir これも本当の違いはありません。後で私のodroid xu4でExynos5422 CPUをチェックすることができます。
最新のCPUは分岐予測子を持っているということです。データと命令の両方をプリフェッチするためのロジックはたくさんありますが、最新のx86 CPUは、これに関してはかなり賢明です。 ARMやGPUのような一部のより薄いアーキテクチャはこれに対して脆弱かもしれません。しかし、それは本当にコンパイラとターゲットシステムの両方に大きく依存しています。
分岐順序の最適化は非常に脆弱で短命であると私は言うでしょう。本当に微調整するための手順としてのみ実行してください。
コード:
#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <functional>
using namespace std;
int main()
{
//Generate a vector of random integers from 1 to 100
random_device rnd_device;
mt19937 rnd_engine(rnd_device());
uniform_int_distribution<int> rnd_dist(1, 100);
auto gen = std::bind(rnd_dist, rnd_engine);
vector<int> Rand_vec(5000);
generate(begin(Rand_vec), end(Rand_vec), gen);
volatile int nLow, nMid, nHigh;
//Count the number of values in each of three different ranges
//Run the test a few times
for (int n = 0; n != 10; ++n) {
//Run the test again, but now sort the conditional statements in reverse-order of likelyhood
{
nLow = nMid = nHigh = 0;
auto start = chrono::high_resolution_clock::now();
for (int& i : Rand_vec) {
if (i >= 95) ++nHigh; //Least likely branch
else if (i < 20) ++nLow;
else if (i >= 20 && i < 95) ++nMid; //Most likely branch
}
auto end = chrono::high_resolution_clock::now();
cout << "Reverse-sorted: \t" << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << "ns" << endl;
}
{
//Sort the conditional statements in order of likelyhood
nLow = nMid = nHigh = 0;
auto start = chrono::high_resolution_clock::now();
for (int& i : Rand_vec) {
if (i >= 20 && i < 95) ++nMid; //Most likely branch
else if (i < 20) ++nLow;
else if (i >= 95) ++nHigh; //Least likely branch
}
auto end = chrono::high_resolution_clock::now();
cout << "Sorted:\t\t\t" << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << "ns" << endl;
}
cout << endl;
}
}
私の5セントだけ。文が次のものに依存する必要がある場合は、順序付けの効果があるようです。
各ifステートメントの確率。
繰り返し回数、分岐予測子が起動する可能性があります。
ありそうも思わないコンパイラヒント、すなわちコードレイアウト。
これらの要因を調べるために、私は次の機能をベンチマークしました。
for (i = 0; i < data_sz * 1024; i++) {
if (data[i] < check_point) // highly likely
s += 3;
else if (data[i] > check_point) // samewhat likely
s += 2;
else if (data[i] == check_point) // very unlikely
s += 1;
}
for (i = 0; i < data_sz * 1024; i++) {
if (data[i] == check_point) // very unlikely
s += 1;
else if (data[i] > check_point) // samewhat likely
s += 2;
else if (data[i] < check_point) // highly likely
s += 3;
}
for (i = 0; i < data_sz * 1024; i++) {
if (likely(data[i] < check_point)) // highly likely
s += 3;
else if (data[i] > check_point) // samewhat likely
s += 2;
else if (unlikely(data[i] == check_point)) // very unlikely
s += 1;
}
for (i = 0; i < data_sz * 1024; i++) {
if (unlikely(data[i] == check_point)) // very unlikely
s += 1;
else if (data[i] > check_point) // samewhat likely
s += 2;
else if (likely(data[i] < check_point)) // highly likely
s += 3;
}
データ配列には、0から100までの乱数が含まれています。
const int RANGE_MAX = 100;
uint8_t data[DATA_MAX * 1024];
static void data_init(int data_sz)
{
int i;
srand(0);
for (i = 0; i < data_sz * 1024; i++)
data[i] = Rand() % RANGE_MAX;
}
次の結果は、Intel i5 @ 3,2 GHzおよびG ++ 6.3.0のものです。最初の引数はcheck_point(つまり、蓋然性の高いif文に対する確率%%)、2番目の引数はdata_sz(つまり反復回数)です。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4 4660 ns 4658 ns 150948
ordered_ifs/50/8 25636 ns 25635 ns 27852
ordered_ifs/75/4 4326 ns 4325 ns 162613
ordered_ifs/75/8 18242 ns 18242 ns 37931
ordered_ifs/100/4 1673 ns 1673 ns 417073
ordered_ifs/100/8 3381 ns 3381 ns 207612
reversed_ifs/50/4 5342 ns 5341 ns 126800
reversed_ifs/50/8 26050 ns 26050 ns 26894
reversed_ifs/75/4 3616 ns 3616 ns 193130
reversed_ifs/75/8 15697 ns 15696 ns 44618
reversed_ifs/100/4 3738 ns 3738 ns 188087
reversed_ifs/100/8 7476 ns 7476 ns 93752
ordered_ifs_with_hints/50/4 5551 ns 5551 ns 125160
ordered_ifs_with_hints/50/8 23191 ns 23190 ns 30028
ordered_ifs_with_hints/75/4 3165 ns 3165 ns 218492
ordered_ifs_with_hints/75/8 13785 ns 13785 ns 50574
ordered_ifs_with_hints/100/4 1575 ns 1575 ns 437687
ordered_ifs_with_hints/100/8 3130 ns 3130 ns 221205
reversed_ifs_with_hints/50/4 6573 ns 6572 ns 105629
reversed_ifs_with_hints/50/8 27351 ns 27351 ns 25568
reversed_ifs_with_hints/75/4 3537 ns 3537 ns 197470
reversed_ifs_with_hints/75/8 16130 ns 16130 ns 43279
reversed_ifs_with_hints/100/4 3737 ns 3737 ns 187583
reversed_ifs_with_hints/100/8 7446 ns 7446 ns 93782
4K反復および(ほぼ)100%の非常に好意的なステートメントの可能性に対して、その差は223%と非常に大きいです。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/100/4 1673 ns 1673 ns 417073
reversed_ifs/100/4 3738 ns 3738 ns 188087
4K反復および50%の好評ステートメントの確率では、差は約14%です。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4 4660 ns 4658 ns 150948
reversed_ifs/50/4 5342 ns 5341 ns 126800
(ほぼ)100%の確率の高いステートメントの4Kと8Kの繰り返しの違いは、(予想どおり)約2倍です。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/100/4 1673 ns 1673 ns 417073
ordered_ifs/100/8 3381 ns 3381 ns 207612
しかし、50%の確率の高いステートメントの4Kと8Kの繰り返しの違いは5,5倍です。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4 4660 ns 4658 ns 150948
ordered_ifs/50/8 25636 ns 25635 ns 27852
何故ですか?分岐予測が失敗するため。上記の各ケースでの分岐ミスは次のとおりです。
ordered_ifs/100/4 0.01% of branch-misses
ordered_ifs/100/8 0.01% of branch-misses
ordered_ifs/50/4 3.18% of branch-misses
ordered_ifs/50/8 15.22% of branch-misses
だから私のi5では、分岐予測子はそれほどありそうもない分岐や大きなデータセットに対しては見事に失敗します。
4K反復の場合、結果は50%の確率でやや悪くなり、100%の確率でやや良くなります。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4 4660 ns 4658 ns 150948
ordered_ifs/100/4 1673 ns 1673 ns 417073
ordered_ifs_with_hints/50/4 5551 ns 5551 ns 125160
ordered_ifs_with_hints/100/4 1575 ns 1575 ns 437687
しかし、8K反復では、結果は常に少し良くなります。
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/8 25636 ns 25635 ns 27852
ordered_ifs/100/8 3381 ns 3381 ns 207612
ordered_ifs_with_hints/50/8 23191 ns 23190 ns 30028
ordered_ifs_with_hints/100/8 3130 ns 3130 ns 221205
そのため、ヒントも役に立ちますが、ほんの少しだけです。
全体的な結論は次のとおりです。結果が驚くかもしれないので、常にコードをベンチマークしてください。
それが役立つことを願っています。
ここに他のいくつかの答えに基づいて、それは唯一の本当の答えがあるように見えます:それは依存します。これは少なくとも次の要素に依存します(ただし必ずしもこの重要度の順序には限りません)。
確実に知る唯一の方法は、できればコードが最終的に実行される予定のシステムと同一の(または非常によく似た)システムで、特定のケースをベンチマークすることです。ハードウェア、オペレーティングシステムなどが異なるさまざまなシステムで実行することを目的としている場合は、複数のバリエーションにまたがってベンチマークを実行してどれが最適かを確認することをお勧めします。あるタイプのシステム上のある順序と別のタイプのシステム上の別の順序でコードをコンパイルすることをお勧めします。
私の個人的な経験則(ほとんどの場合、ベンチマークがない場合)は、以下に基づいて注文することです。
私が高性能コードのためにこれを解決するのを私が通常見る方法は最も読みやすい順序を保ちながらコンパイラへのヒントを提供することです。これが Linuxカーネル の一例です。
if (likely(access_ok(VERIFY_READ, from, n))) {
kasan_check_write(to, n);
res = raw_copy_from_user(to, from, n);
}
if (unlikely(res))
memset(to + (n - res), 0, res);
ここでは、アクセスチェックが成功し、res
にエラーが返されないことを前提としています。これらのif句のいずれかを並べ替えようとするとコードが混乱するだけですが、likely()
マクロとunlikely()
マクロは、実際には通常のケースで、例外は何であるかを指摘することで、読みやすさに役立ちます。
これらのマクロのLinux実装は GCC特有の機能 を使用します。 clangとIntel Cコンパイラは同じ構文をサポートしているようですが、 MSVCにはそのような機能はありません 。
また、あなたのコンパイラとあなたがコンパイルしようとしているプラットフォームにも依存します。
理論的には、最も可能性の高い条件では、コントロールのジャンプをできるだけ少なくする必要があります。
通常、最も可能性の高い状態が最初になります。
if (most_likely) {
// most likely instructions
} else …
最も人気のあるASMは、条件がtrueのときにジャンプする条件分岐に基づいています。そのCコードは、おそらくそのような疑似asmに変換されます。
jump to ELSE if not(most_likely)
// most likely instructions
jump to end
ELSE:
…
これは、ジャンプによって実行パイプラインがキャンセルされ、プログラムカウンタが変更されたために停止するためです(本当に一般的なパイプラインをサポートするアーキテクチャの場合)。それはコンパイラに関するもので、これは統計的に最も可能性の高い条件でコントロールのジャンプが少なくなるようにするための高度な最適化を適用するかもしれないししないかもしれません。
私はLik32コードを使って自分のマシンでテストを再実行することにしました。私のウィンドウやコンパイラが高解像度は1msだと思っていたので、私はそれを変更しなければなりませんでした。
mingw32-g ++。exe -O3 -Wall -std = c ++ 11 -fexceptions -g
vector<int> Rand_vec(10000000);
GCCは両方のオリジナルコードで同じ変換を行いました。
最初の2つの条件だけがテストされることに注意してください。3番目の条件は常に成り立ちます。GCCはここでは一種のSherlockです。
逆
.L233:
mov DWORD PTR [rsp+104], 0
mov DWORD PTR [rsp+100], 0
mov DWORD PTR [rsp+96], 0
call std::chrono::_V2::system_clock::now()
mov rbp, rax
mov rax, QWORD PTR [rsp+8]
jmp .L219
.L293:
mov edx, DWORD PTR [rsp+104]
add edx, 1
mov DWORD PTR [rsp+104], edx
.L217:
add rax, 4
cmp r14, rax
je .L292
.L219:
mov edx, DWORD PTR [rax]
cmp edx, 94
jg .L293 // >= 95
cmp edx, 19
jg .L218 // >= 20
mov edx, DWORD PTR [rsp+96]
add rax, 4
add edx, 1 // < 20 Sherlock
mov DWORD PTR [rsp+96], edx
cmp r14, rax
jne .L219
.L292:
call std::chrono::_V2::system_clock::now()
.L218: // further down
mov edx, DWORD PTR [rsp+100]
add edx, 1
mov DWORD PTR [rsp+100], edx
jmp .L217
And sorted
mov DWORD PTR [rsp+104], 0
mov DWORD PTR [rsp+100], 0
mov DWORD PTR [rsp+96], 0
call std::chrono::_V2::system_clock::now()
mov rbp, rax
mov rax, QWORD PTR [rsp+8]
jmp .L226
.L296:
mov edx, DWORD PTR [rsp+100]
add edx, 1
mov DWORD PTR [rsp+100], edx
.L224:
add rax, 4
cmp r14, rax
je .L295
.L226:
mov edx, DWORD PTR [rax]
lea ecx, [rdx-20]
cmp ecx, 74
jbe .L296
cmp edx, 19
jle .L297
mov edx, DWORD PTR [rsp+104]
add rax, 4
add edx, 1
mov DWORD PTR [rsp+104], edx
cmp r14, rax
jne .L226
.L295:
call std::chrono::_V2::system_clock::now()
.L297: // further down
mov edx, DWORD PTR [rsp+96]
add edx, 1
mov DWORD PTR [rsp+96], edx
jmp .L224
したがって、最後のケースでは予測分岐が不要であることを除けば、これはあまり意味がありません。
今私はifの6つすべての組み合わせを試してみました、トップ2は元の逆でソートされています。 highは95以上、lowは20以下、midは20-94、それぞれ100000000回の繰り返しです。
high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 44000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 45000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 46000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 43000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 48000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 45000000ns
low, high, mid: 45000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns
high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns
1900020, 7498968, 601012
Process returned 0 (0x0) execution time : 2.899 s
Press any key to continue.
それでは、なぜ順位が高い、低い、中程度で、それからもっと速いのか(わずかに)
最も予測不可能なのは最後で、したがって分岐予測子を通過することは決してないためです。
if (i >= 95) ++nHigh; // most predictable with 94% taken
else if (i < 20) ++nLow; // (94-19)/94% taken ~80% taken
else if (i >= 20 && i < 95) ++nMid; // never taken as this is the remainder of the outfalls.
それで、枝は、とられて、とられて、残りがと予測されるでしょう。
6%+(0.94 *)20%の予測ミス。
「ソート済み」
if (i >= 20 && i < 95) ++nMid; // 75% not taken
else if (i < 20) ++nLow; // 19/25 76% not taken
else if (i >= 95) ++nHigh; //Least likely branch
分岐は取られていない、取られていないとシャーロックで予測されます。
25%+(0.75 *)24%の予測ミス
18〜23%の差(約9%の測定差)を与えますが、予測ミス%ではなく周期を計算する必要があります。
17サイクルが私のNehalem CPUのペナルティを誤って予測し、各チェックが発行するのに1サイクル(4-5命令)かかり、ループも1サイクルかかると仮定しましょう。データの依存関係はカウンタとループ変数ですが、いったん誤予測が邪魔にならない場合は、タイミングには影響しません。
そのため、「リバース」の場合は、タイミングを取得します(これは、「コンピュータアーキテクチャ:定量的アプローチIIRC」で使用されている公式でなければなりません)。
mispredict*penalty+count+loop
0.06*17+1+1+ (=3.02)
(propability)*(first check+mispredict*penalty+count+loop)
(0.19)*(1+0.20*17+1+1)+ (= 0.19*6.4=1.22)
(propability)*(first check+second check+count+loop)
(0.75)*(1+1+1+1) (=3)
= 7.24 cycles per iteration
"sorted"についても同じです。
0.25*17+1+1+ (=6.25)
(1-0.75)*(1+0.24*17+1+1)+ (=.25*7.08=1.77)
(1-0.75-0.19)*(1+1+1+1) (= 0.06*4=0.24)
= 8.26
(8.26-7.24)/8.26 = 13.8%対測定された〜9%(測定値に近い!?!).
だから、OPの自明は自明ではない。
これらのテストでは、より複雑なコードやより多くのデータ依存関係を持つ他のテストは確かに異なるので、ケースを測定します。
テストの順序を変更すると結果が変わりましたが、それはループ開始のアライメントが異なるためである可能性があります。これは、すべての新しいIntel CPUで理想的には16バイトである必要があります。
あなたが好きな論理的な順序でそれらを入れなさい。確かに、分岐は遅くなるかもしれませんが、分岐はあなたのコンピュータがしている仕事の大部分であるべきではありません。
あなたがコードのパフォーマンス上重要な部分に取り組んでいるならば、それから確かに論理的な順序、プロファイルに基づく最適化と他のテクニックを使います、しかし一般的なコードのために、私はそれが本当によりスタイル的な選択だと思います。
If-else文の相対確率がすでにわかっている場合は、パフォーマンス上の理由から、1つの条件(真の条件)のみをチェックするため、ソートされた方法を使用することをお勧めします。
ソートされていない方法では、コンパイラはすべての条件を不必要にチェックし、時間がかかります。