على وجه التحديد ، إذا كان لديّ سلسلة من عبارات 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
يبدو من الواضح أن الإصدار المصنف سيكون أسرع ، ولكن من أجل سهولة القراءة أو وجود آثار جانبية ، قد نرغب في طلبها بطريقة غير مثالية. من الصعب أيضًا معرفة مدى أداء وحدة المعالجة المركزية بالتنبؤ بالفرع حتى تقوم بتشغيل الشفرة بالفعل.
لذا ، أثناء تجربة ذلك ، انتهيت إلى الإجابة على سؤالي الخاص بحالة معينة ، ومع ذلك أود سماع آراء/رؤى أخرى أيضًا.
هام: يفترض هذا السؤال أن عبارات if
يمكن إعادة ترتيبها بشكل تعسفي دون أن يكون لها أي تأثيرات أخرى على سلوك البرنامج. في إجابتي ، الاختبارات الشرطية الثلاثة متبادلة ولا تنتج أي آثار جانبية. بالتأكيد ، إذا كان يجب تقييم البيانات بترتيب معين لتحقيق بعض السلوك المرغوب فيه ، فستكون مسألة الكفاءة موضع نقاش.
كقاعدة عامة ، تفترض معظم وحدات المعالجة المركزية Intel - إن لم تكن كلها - أن الفروع الأمامية لا تؤخذ في المرة الأولى التي تراها فيها. انظر عمل جودبولت .
بعد ذلك ، ينتقل الفرع إلى ذاكرة التخزين المؤقت للتنبؤ بالفرع ، ويتم استخدام السلوك السابق لإبلاغ التنبؤ بالفرع في المستقبل.
لذلك في حلقة ضيقة ، سيكون تأثير misordering صغير نسبيا. سوف يتنبأ متنبئ الفرع بفروع الفروع الأكثر ترجيحًا ، وإذا كان لديك قدر غير قليل من العمل في الحلقة ، فإن الفروق الصغيرة لن تزيد كثيرًا.
في التعليمات البرمجية العامة ، فإن معظم المترجمين بشكل افتراضي (يفتقر إلى سبب آخر) سيطلبون رمز الجهاز المنتج تقريبًا بالطريقة التي طلبت بها في الكود. وبالتالي إذا كانت العبارات هي فروع للأمام عندما تفشل.
لذلك يجب عليك طلب فروعك بترتيب الاحتمال المتناقص للحصول على أفضل تنبؤ للفرع من "اللقاء الأول".
إن علامة الميكروفون الصغيرة التي يتم تشغيلها بإحكام عدة مرات على مجموعة من الشروط وتقوم بعمل تافه سوف تهيمن عليها تأثيرات صغيرة من عدد التعليمات وما شابه ذلك ، والقليل في طريق مشكلات التنبؤ النسبي للفرع. في هذه الحالة ، يجب عليك الملف الشخصي ، لأن قواعد الإبهام لن تكون موثوقة.
علاوة على ذلك ، ينطبق التمرين والعديد من التحسينات الأخرى على حلقات ضيقة صغيرة.
لذا ، في الكود العام ، ضع الكود الأكثر ترجيحًا ضمن كتلة if
، وسيؤدي ذلك إلى عدد أقل من توقعات الفروع غير المخزنة مؤقتًا. في الحلقات الضيقة ، اتبع القاعدة العامة للبدء ، وإذا كنت بحاجة إلى معرفة المزيد ، فلديك خيار صغير غير ملف التعريف.
بطبيعة الحال ، كل هذا يخرج من النافذة إذا كانت بعض الاختبارات أرخص بكثير من غيرها.
قمت بإجراء الاختبار التالي حتى وقت تنفيذ كتلتين مختلفتين if
...else if
block ، واحدة مرتبة بترتيب الاحتمال ، والآخر مرتبة بترتيب عكسي:
#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 ، قمت أيضًا بتبديل ترتيب الاختبارين ، مما يحدث فرقًا ملحوظًا (22٪ مقابل 28٪). تم تشغيل الكود تحت ويندوز 7 على جهاز Intel Xeon E5-2697 v2. هذا بالطبع خاص بكل مشكلة ولا يجب تفسيره كإجابة قاطعة.
لا يجب عليك ، إلا إذا كنت متأكدًا حقًا من تأثر النظام المستهدف. افتراضيا الذهاب عن طريق القراءة.
أشك بشدة في نتائجك. لقد قمت بتعديل مثالك قليلاً ، لذا فإن عكس التنفيذ أسهل. Ideone بالأحرى يظهر أن الترتيب العكسي أسرع ، وإن لم يكن كثيرًا. على أشواط معينة حتى هذا انقلبت في بعض الأحيان. أود أن أقول أن النتائج غير حاسمة. coliru تقارير لا يوجد فرق حقيقي كذلك. يمكنني التحقق من Exynos5422 CPU على بلدي xu4 odroid في وقت لاحق.
الشيء هو أن وحدات المعالجة المركزية الحديثة لها تنبئ فرع. يوجد الكثير من المنطق المخصص لإحضار البيانات والتعليمات بشكل مسبق ، كما أن وحدات المعالجة المركزية x86 الحديثة ذكية نوعًا ما ، عندما يتعلق الأمر بذلك. قد تكون بعض البنى الأقل حجماً مثل أرمينيا أو وحدات معالجة الرسومات عرضة لهذا. لكنه يعتمد بشكل كبير حقا على كل من النظام المترجم والهدف.
أود أن أقول إن تحسين ترتيب الفروع هش للغاية وسريع الزوال. افعل ذلك فقط كخطوة مضبوطة حقًا.
الشفرة:
#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 سنتات يبدو أن تأثير الطلب إذا كانت البيانات يجب أن تعتمد على:
احتمالية كل بيان.
عدد التكرارات ، لذلك يمكن للتنبؤ فرع ركلة.
تلميحات برنامج التحويل المحتمل/المحتمل ، على سبيل المثال تخطيط الكود.
لاستكشاف هذه العوامل ، قمت بتقييم الوظائف التالية:
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 (أي الاحتمال في ٪٪ للرجح المحتمل للبيان) ، والوسيطة الثانية هي 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
الفرق بين التكرارات 4K و 8K لـ (تقريبًا) احتمال 100٪ للبيان المحبوب للغاية هو حوالي مرتين (كما هو متوقع):
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
ordered_ifs/100/4 1673 ns 1673 ns 417073
ordered_ifs/100/8 3381 ns 3381 ns 207612
ولكن الفرق بين التكرارات 4K و 8K لاحتمال 50 ٪ من العبارة محبوب للغاية هو 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 kernel :
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
. تحاول إعادة ترتيب أيٍّ من هذين الخيارين إذا كانت الجمل تخلط بين الشفرة فقط ، لكن وحدات الماكرو likely()
و unlikely()
تساعد في الواقع على القراءة من خلال الإشارة إلى الحالة الطبيعية وما هو الاستثناء.
يستخدم تطبيق Linux لتلك وحدات الماكرو ميزات خاصة بمجلس التعاون الخليجي . يبدو أن برنامج التحويل البرمجي clang و Intel C يدعم بناء الجملة نفسه ، لكن لا يحتوي MSVC على هذه الميزة .
يعتمد أيضًا على برنامج التحويل البرمجي والنظام الأساسي الذي تجمعه.
من الناحية النظرية ، فإن الحالة الأكثر ترجيحًا تجعل السيطرة تقفز إلى أدنى درجة ممكنة.
عادةً ما يكون الشرط الأكثر احتمالًا هو الأول:
if (most_likely) {
// most likely instructions
} else …
تعتمد أسماء asm الأكثر شعبية على الفروع الشرطية التي تقفز عندما تكون الحالة true . من المحتمل أن تترجم شفرة C إلى مثل هذا الاسم الزائف:
jump to ELSE if not(most_likely)
// most likely instructions
jump to end
ELSE:
…
وذلك لأن القفزات تجعل وحدة المعالجة المركزية تلغي خط أنابيب التنفيذ والمماطلة لأن عداد البرنامج قد تغير (بالنسبة للمعماريات التي تدعم خطوط الأنابيب الشائعة بالفعل). ثم يتعلق الأمر بالمترجم ، الذي قد يقوم أو لا يطبق بعض التحسينات المعقدة حول وجود الشرط الإحصائي على الأرجح للحصول على عنصر التحكم مما يؤدي إلى حدوث قفزات أقل.
قررت إعادة تشغيل الاختبار على الجهاز الخاص بي باستخدام رمز Lik32. اضطررت لتغييره بسبب بلدي ويندوز أو المترجم التفكير عالية الدقة هو 1ms ، وذلك باستخدام
mingw32-g ++. exe -O3 -Wall -std = c ++ 11 -exexions -g
vector<int> Rand_vec(10000000);
حققت دول مجلس التعاون الخليجي نفس التحول على كلا الكودتين الأصليتين.
لاحظ أن الشرطين الأولين فقط يتم اختبارهما لأن الشرط الثالث يجب أن يكون دائمًا صحيحًا ، دول مجلس التعاون الخليجي هي نوع من شيرلوك هنا.
عكس
.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
لذلك هذا لا يخبرنا الكثير إلا أن الحالة الأخيرة لا تحتاج إلى فرع التنبؤ.
الآن جربت جميع التوليفات الـ 6 الخاصة بـ if's ، أعلى 2 هما العكس الأصلي وفرزها. ارتفاع> = 95 ، منخفض <20 ، منتصف 20-94 مع 10000000 تكرار لكل منهما.
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 الخاصة بي وأن كل عملية تدقيق تستغرق دورة واحدة لإصدارها (4-5 تعليمات) وأن الحلقة تأخذ دورة واحدة أيضًا. تبعيات البيانات هي العدادات ومتغيرات الحلقة ، ولكن بمجرد أن تكون الأخطاء الخاطئة خارجة عن الطريق فلن تؤثر على التوقيت.
لذلك بالنسبة لـ "العكس" ، نحصل على التوقيت (يجب أن تكون هذه هي الصيغة المستخدمة في هندسة الكمبيوتر: النهج الكمي 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
والشيء نفسه بالنسبة لـ "مرتبة"
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٪ مقاسة (قريبة من المقاسة!؟!).
إذن ما هو واضح من البروتوكول الاختياري غير واضح.
مع هذه الاختبارات ، ستكون الاختبارات الأخرى التي تحتوي على تعليمات برمجية أكثر تعقيدًا أو مزيدًا من تبعيات البيانات مختلفة بالتأكيد ، لذا قم بقياس حالتك.
غير تغيير ترتيب الاختبار النتائج ، لكن قد يكون ذلك بسبب محاذاة مختلفة من بداية الحلقة والتي يجب أن تكون محاذاة 16 بايت على جميع وحدات المعالجة المركزية Intel Intel الأحدث ولكن ليس في هذه الحالة.
وضعها في أي ترتيب منطقي تريد. بالتأكيد ، قد يكون الفرع أبطأ ، ولكن لا ينبغي أن يكون التفريع هو الجزء الأكبر من العمل الذي يقوم به جهاز الكمبيوتر الخاص بك.
إذا كنت تعمل على جزء هام من الأداء من التعليمات البرمجية ، فبالتأكيد استخدم الترتيب المنطقي والتحسين الموجه لملف التعريف وغير ذلك من التقنيات ، لكن بالنسبة إلى الكود العام ، أعتقد أنه اختيارًا أفضل من حيث الأسلوب.
إذا كنت تعرف بالفعل الاحتمال النسبي لبيان if-else ، فمن الأفضل لغرض الأداء استخدام الطريقة التي تم فرزها ، حيث إنها ستقوم بفحص شرط واحد فقط (الشرط الحقيقي).
بطريقة غير مصنفة سوف يقوم المترجم بفحص جميع الشروط دون داع وسيستغرق بعض الوقت.