आदेश देने का क्या प्रभाव है अगर ... और अगर संभावना द्वारा बयान?


187

विशेष रूप से, अगर मेरे पास 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कार्यक्रम के व्यवहार पर किसी अन्य प्रभाव के बिना बयानों को मनमाने ढंग से पुन: व्यवस्थित किया जा सकता है। मेरे जवाब में, तीन सशर्त परीक्षण पारस्परिक रूप से अनन्य हैं और कोई दुष्प्रभाव नहीं पैदा करते हैं। निश्चित रूप से, यदि कुछ वांछित व्यवहार को प्राप्त करने के लिए कथनों का मूल्यांकन एक निश्चित क्रम में किया जाना चाहिए, तो दक्षता का मुद्दा मूक है।


35
आप एक नोट जोड़ना चाह सकते हैं कि स्थितियां परस्पर अनन्य हैं, अन्यथा दो संस्करण समतुल्य नहीं हैं
idclev 463035818

28
यह बहुत ही रोचक है कि कैसे एक स्व उत्तर वाले प्रश्न को एक घंटे में, बल्कि खराब उत्तर के साथ 20+ अपवोट मिला। ओपी पर कुछ भी नहीं कह रहा है लेकिन अपवित्रों को बैंड वैगन पर कूदने से सावधान रहना चाहिए। सवाल दिलचस्प हो सकता है, लेकिन परिणाम संदिग्ध हैं।
luk32

3
मेरा मानना ​​है कि इसे शॉर्ट-सर्किट मूल्यांकन के रूप में वर्णित किया जा सकता है क्योंकि एक तुलना को रोकना एक अलग तुलना को मारने से इनकार करता है। मैं व्यक्तिगत रूप से इस तरह के कार्यान्वयन का पक्ष लेता हूं जब एक तेजी से तुलना, चलो बूलियन कहते हैं, मुझे एक अलग तुलना में जाने से रोक सकता है जिसमें एक संसाधन-भारी स्ट्रिंग हेरफेर, रेगेक्स या डेटाबेस इंटरैक्शन शामिल हो सकता है।
मंकीज़ियस

11
कुछ संकलक ली गई शाखाओं पर आंकड़े इकट्ठा करने की क्षमता प्रदान करते हैं और इन वापस संकलक को खिलाते हैं ताकि वे बेहतर अनुकूलन कर सकें।

11
अगर आपके लिए इस तरह का प्रदर्शन अच्छा है, तो आपको संभवतः प्रोफ़ाइल निर्देशित अनुकूलन का प्रयास करना चाहिए और संकलक के परिणाम के साथ अपने मैनुअल परिणाम की तुलना करना चाहिए
जस्टिन

जवाबों:


96

एक सामान्य नियम के रूप में, अधिकांश अगर सभी इंटेल सीपीयू नहीं मानते हैं कि पहली बार शाखाएं उन्हें नहीं दिखाई देती हैं। गॉडबोल्ट का काम देखें ।

उसके बाद, शाखा एक शाखा भविष्यवाणी कैश में चली जाती है, और भविष्य के शाखा पूर्वानुमान को सूचित करने के लिए पिछले व्यवहार का उपयोग किया जाता है।

तो एक तंग लूप में, गलत काम का प्रभाव अपेक्षाकृत कम होने वाला है। शाखा के भविष्यवक्ता यह जानने जा रहे हैं कि शाखाओं का कौन सा सेट सबसे अधिक संभावना है, और यदि आपके पास लूप में गैर-तुच्छ काम है, तो छोटे अंतर बहुत अधिक नहीं जोड़ेंगे।

सामान्य कोड में, डिफ़ॉल्ट रूप से अधिकांश कंपाइलर (किसी अन्य कारण की कमी) उत्पादित मशीन कोड को मोटे तौर पर उस तरह से आदेश देंगे जैसे आपने अपने कोड में दिया था। इस प्रकार यदि कथन विफल होने पर अग्रेषित शाखाएँ हैं।

इसलिए आपको "पहली मुठभेड़" से सर्वश्रेष्ठ शाखा भविष्यवाणी प्राप्त करने की संभावना कम करने के क्रम में अपनी शाखाओं का आदेश देना चाहिए।

एक माइक्रोबैनचमार्क जो कई बार परिस्थितियों के एक सेट पर कसकर लूप करता है और तुच्छ काम करता है, जो निर्देश की गिनती और इसी तरह के छोटे प्रभावों द्वारा हावी होने जा रहा है, और रिश्तेदार शाखा की भविष्यवाणी के मुद्दों के रास्ते में बहुत कम है। तो इस मामले में आपको प्रोफाइल करना होगा , क्योंकि अंगूठे के नियम विश्वसनीय नहीं होंगे।

उसके ऊपर, वेक्टरकरण और कई अन्य अनुकूलन छोटे तंग छोरों पर लागू होते हैं।

इसलिए सामान्य कोड में, ifब्लॉक के भीतर सबसे अधिक संभावना कोड डालते हैं , और इसका परिणाम सबसे कम संयुक्त कैश्ड शाखा भविष्यवाणी याद होगा। तंग छोरों में, शुरू करने के लिए सामान्य नियम का पालन करें, और यदि आपको अधिक जानने की आवश्यकता है तो आपके पास प्रोफ़ाइल के अलावा बहुत कम विकल्प हैं।

स्वाभाविक रूप से यह सब खिड़की से बाहर चला जाता है अगर कुछ परीक्षण दूसरों की तुलना में बहुत सस्ते हैं।


19
यह भी विचार करने के लायक है कि परीक्षण स्वयं कितने महंगे हैं: यदि एक परीक्षण केवल थोड़ा अधिक होने की संभावना है, लेकिन बहुत अधिक महंगा है, तो यह पहले दूसरे परीक्षण को डालने के लायक हो सकता है, क्योंकि महंगी परीक्षा नहीं करने से होने वाली बचत संभावित रूप से आगे निकल जाएगी शाखा भविष्यवाणी आदि से बचत
psmears

लिंक आप अपने निष्कर्ष का समर्थन नहीं करता प्रदान की एक सामान्य नियम के रूप में, सबसे अगर नहीं सभी इंटेल सीपीयू आगे शाखाओं पहली बार वे उन्हें देख नहीं रखा जाता मान । वास्तव में यह केवल अपेक्षाकृत अस्पष्ट एरेन्डेल सीपीयू के लिए सही है, जिसके परिणाम पहले दिखाए गए हैं। मुख्यधारा के आइवी ब्रिज और हैसवेल परिणाम बिल्कुल भी समर्थन नहीं करते हैं। हैसवेल अनदेखी शाखाओं के लिए "हमेशा के माध्यम से गिरावट का अनुमान लगाने" के बहुत करीब दिखता है, और आइवी ब्रिज बिल्कुल भी स्पष्ट नहीं है।
BeeOnRope

यह आमतौर पर समझा जाता है कि सीपीयू वास्तव में स्थिर भविष्यवाणियों का उपयोग नहीं कर रहे हैं जैसा कि उन्होंने अतीत में किया था। वास्तव में आधुनिक इंटेल शायद एक संभाव्य TAGE भविष्यवक्ता की तरह कुछ का उपयोग कर रहा है। आपके पास विभिन्न इतिहास तालिकाओं में बस हैश इतिहास है और सबसे लंबे इतिहास के साथ मेल खाता है। अलियासिंग से बचने की कोशिश करने के लिए यह "टैग" का उपयोग करता है, लेकिन टैग में केवल कुछ बिट्स होते हैं। यदि आप सभी इतिहास की लंबाई को याद करते हैं, तो कुछ डिफ़ॉल्ट भविष्यवाणी शायद की जाती है जो जरूरी नहीं कि शाखा दिशा पर निर्भर करती है (हसवेल पर हम कह सकते हैं कि यह स्पष्ट रूप से नहीं है)।
BeeOnRope

44

मैंने दो अलग-अलग 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;
}

/ O2 के साथ MSVC2017 का उपयोग करते हुए, परिणाम बताते हैं कि सॉर्ट किए गए संस्करण लगातार अनसोल्ड संस्करण की तुलना में लगभग 28% तेज है। प्रति luk32 की टिप्पणी के अनुसार, मैंने दो परीक्षणों के क्रम को भी बदल दिया, जो ध्यान देने योग्य अंतर (22% बनाम 28%) बनाता है। कोड एक इंटेल Xeon E5-2697 v2 पर विंडोज 7 के तहत चलाया गया था। यह, निश्चित रूप से, बहुत समस्या-विशिष्ट है और इसे निर्णायक उत्तर के रूप में नहीं समझा जाना चाहिए।


9
ओपी को हालांकि सावधान रहना चाहिए, क्योंकि एक if... else ifबयान को बदलने से कोड के माध्यम से तर्क प्रवाह पर काफी प्रभाव पड़ सकता है। unlikelyजांच अक्सर नहीं आ सकता है, लेकिन एक व्यापार के लिए जांच की जरूरत हो सकता है unlikelyपहले दूसरों के लिए जाँच से पहले शर्त।
ल्यूक टी ब्रुक्स

21
30% तेज? आप इसका मतलब यह है कि अगर यह प्रदर्शन करने के लिए नहीं था बयानों की तुलना में यह लगभग% का तेजी से था? एक बहुत ही उचित परिणाम लगता है।
UKMonkey

5
आपने इसे कैसे बेंचमार्क किया? कौन सा संकलक, सीपीयू, आदि? मुझे पूरा यकीन है कि यह परिणाम पोर्टेबल नहीं है।
luk32

12
इस माइक्रोबेंक्रमार्क के साथ एक समस्या यह है कि सीपीयू यह काम करने जा रहा है कि कौन सी शाखा सबसे अधिक संभावना है और जब आप बार-बार लूप करते हैं तो इसे कैश करें। यदि शाखाएं जहां एक छोटे से तंग लूप में जांच नहीं की जाती है, तो शाखा भविष्यवाणी कैश उन्हें इसमें नहीं हो सकती है, और सीपीयू शून्य शाखा भविष्यवाणी कैश मार्गदर्शन के साथ गलत अनुमान लगाने पर लागत बहुत अधिक हो सकती है।
यक्क - एडम नेवरामोंट

6
यह बेंचमार्क बहुत विश्वसनीय नहीं है। 6.3.0 gcc के साथ संकलित करना : g++ -O2 -march=native -std=c++14क्रमबद्ध सशर्त कथनों को एक मामूली बढ़त देता है, लेकिन अधिकांश समय, दो रन के बीच प्रतिशत अंतर ~ 5% था। कई बार, यह वास्तव में धीमा था (भिन्नताओं के कारण)। मुझे पूरा यकीन है कि ifइस तरह से एस ऑर्डर करना चिंताजनक नहीं है; PGO संभवतः ऐसे किसी भी मामले को पूरी तरह से संभाल लेगा
जस्टिन

30

नहीं, आपको नहीं करना चाहिए, जब तक कि आप वास्तव में निश्चित नहीं हैं कि लक्ष्य प्रणाली प्रभावित है। डिफ़ॉल्ट रूप से पठनीयता द्वारा जाना।

मुझे आपके परिणामों पर अत्यधिक संदेह है। मैंने आपका उदाहरण थोड़ा संशोधित किया है, इसलिए निष्पादन को उलटना आसान है। Ideone लगातार दिखाता है कि रिवर्स-ऑर्डर तेज है, हालांकि ज्यादा नहीं। कुछ रनों पर भी यह कभी-कभार फ़्लिप हो जाता है। मैं कहता हूं कि परिणाम अनिर्णायक हैं। कोलिरू कोई वास्तविक अंतर नहीं बताता है। मैं Exynos5422 सीपीयू को मेरे ओड्रॉयड एक्सयू 4 पर बाद में जांच सकता हूं।

बात यह है कि आधुनिक सीपीयू में शाखा भविष्यवक्ता होते हैं। डेटा और निर्देशों को पूर्व-लाने के लिए समर्पित बहुत अधिक तर्क है, और आधुनिक 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;
    }
}

मुझे प्रदर्शन में समान ~ 30% का अंतर मिलता है, जब मैं सॉर्ट किए गए और रिवर्स-सॉर्ट किए गए ऑर्डर को ब्लॉक करता है, जैसा कि आपके कोड में किया गया था। मुझे यकीन नहीं है कि Ideone और coliru में कोई अंतर नहीं है।
कार्लटन

निश्चित रूप से दिलचस्प है। मैं अन्य प्रणालियों के लिए कुछ डेटा प्राप्त करने की कोशिश करूंगा, लेकिन मुझे तब तक दिन लग सकते हैं जब तक मुझे इसके साथ खेलना होगा। सवाल दिलचस्प है, खासकर आपके परिणामों के प्रकाश में, लेकिन वे इतने शानदार हैं कि मुझे इसे पार करना पड़ा।
luk32

यदि प्रश्न है कि प्रभाव क्या है? इस सवाल का जवाब नहीं हो सकता है नहीं !
PJTraill

हाँ। लेकिन मुझे मूल प्रश्न के अपडेट के लिए सूचनाएं नहीं मिली हैं। उन्होंने उत्तर सूत्रीकरण को अप्रचलित बना दिया। माफ़ करना। मैं बाद में सामग्री को संपादित करूँगा, यह बताने के लिए कि यह मूल प्रश्न का उत्तर है और कुछ परिणाम दिखाए जो मूल बिंदु साबित हुए।
लूका 32

यह दोहराने के लायक है: "डिफ़ॉल्ट रूप से पठनीयता द्वारा जाना।" पढ़ने योग्य कोड लिखने से आपको मनुष्यों को पार्स करने के लिए अपने कोड को कठिन बनाकर (छोटे शब्दों में) पूर्ण प्रदर्शन को बढ़ावा देने की कोशिश करने से बेहतर परिणाम मिलेगा।
एंड्रयू ब्रूजा

26

बस मेरे 5 सेंट। ऐसा लगता है कि आदेशों का प्रभाव अगर बयानों पर निर्भर होना चाहिए:

  1. प्रत्येक की संभावना अगर बयान।

  2. पुनरावृत्तियों की संख्या, इसलिए शाखा भविष्यवक्ता किक कर सकता है।

  3. संभावना / संभावना संकलक संकेत, यानी कोड लेआउट।

उन कारकों का पता लगाने के लिए, मैंने निम्नलिखित कार्य किए:

ordered_ifs ()

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;
}

reversed_ifs ()

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;
}

ordered_ifs_with_hints ()

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;
}

reversed_ifs_with_hints ()

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 के लिए हैं। पहला तर्क चेक_ पॉइंट (यानी स्टेटमेंट में अत्यधिक संभावना के लिए %% में संभावना) है, दूसरा तर्क डेटा_एसज़ (यानी पुनरावृत्तियों की संख्या) है।

---------------------------------------------------------------------
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

विश्लेषण

1. ऑर्डरिंग मैटर करता है

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

2. Iterations की संख्या मैटर करती है

के लिए 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

लेकिन अत्यधिक पसंद किए गए बयान की 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 पर शाखा भविष्यवक्ता शानदार रूप से बिना-संभावना वाली शाखाओं और बड़े डेटा सेटों के लिए विफल रहता है।

3. संकेत एक बिट मदद करते हैं

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

तो, संकेत भी मदद करते हैं, लेकिन सिर्फ एक छोटा सा।

कुल मिलाकर निष्कर्ष यह है: हमेशा कोड को बेंचमार्क करें, क्योंकि परिणाम आश्चर्यचकित कर सकते हैं।

उम्मीद है की वो मदद करदे।


1
i5 नेहेल्म? i5 स्काइलेक? सिर्फ "i5" कहना बहुत विशिष्ट नहीं है। इसके अलावा, मुझे लगता है कि आपने उपयोग किया है g++ -O2या -O3 -fno-tree-vectorize, लेकिन आपको ऐसा कहना चाहिए।
पीटर कोर्ड्स

दिलचस्प है कि with_hints ऑर्डर किए गए बनाम उलट के लिए अभी भी अलग है। अच्छा होगा अगर आप कहीं न कहीं स्रोत से जुड़े। (उदाहरण के लिए एक गॉडबोल्ट लिंक, अधिमानतः एक पूर्ण-लिंक ताकि लिंक-शॉर्टिंग सड़ न सके।)
पीटर कॉर्डेस

1
तथ्य यह है कि शाखा भविष्यवक्ता 4K इनपुट डेटा आकार पर भी अच्छी तरह से भविष्यवाणी करने में सक्षम है, अर्थात, हजारों में एक अवधि के साथ एक लूप में शाखा परिणामों को याद करके बेंचमार्क को "ब्रेक" करने में सक्षम है, आधुनिक की शक्ति के लिए एक वसीयतनामा है। शाखा के भविष्यवक्ता। ध्यान रखें कि कुछ मामलों में संरेखण की तरह भविष्यवक्ता काफी संवेदनशील होते हैं, इसलिए कुछ परिवर्तनों के बारे में मजबूत निष्कर्ष निकालना कठिन है। उदाहरण के लिए, आपने विभिन्न मामलों में संकेत के विपरीत व्यवहार को देखा, लेकिन यह संकेत यादृच्छिक रूप से बदलते कोड लेआउट द्वारा समझाया जा सकता है जिसने भविष्यवक्ता को प्रभावित किया।
बीएनऑनरोप

1
@PeterCordes मेरा मुख्य बिंदु है जब हम एक बदलाव के परिणामों की भविष्यवाणी करने की कोशिश कर सकते हैं, फिर भी हम बदलाव से पहले और बाद में प्रदर्शन को बेहतर ढंग से मापते हैं ... और आप सही हैं, मुझे उल्लेख करना चाहिए था कि यह -3 और प्रोसेसर के साथ अनुकूलित था is5-4460 @ 3.20GHz
एंड्री

19

यहाँ कुछ अन्य उत्तरों के आधार पर, ऐसा लगता है कि एकमात्र वास्तविक उत्तर है: यह निर्भर करता है । यह कम से कम निम्नलिखित पर निर्भर करता है (हालांकि महत्व के इस क्रम में जरूरी नहीं):

  • प्रत्येक शाखा की सापेक्ष संभावना। यह मूल प्रश्न है जो पूछा गया था। मौजूदा उत्तरों के आधार पर, कुछ ऐसी परिस्थितियाँ प्रतीत होती हैं, जिनके अंतर्गत प्रायिकता द्वारा आदेश देने में मदद मिलती है, लेकिन ऐसा हमेशा नहीं होता है। यदि सापेक्ष संभावनाएं बहुत भिन्न नहीं हैं, तो इससे कोई अंतर नहीं पड़ता है कि वे किस क्रम में हैं। हालांकि, यदि पहली स्थिति 99.999% समय की होती है और अगले वाले का एक अंश शेष है, तो मैं करूंगा। यह मानते हुए कि सबसे पहले एक को लगाना समय के लिहाज से फायदेमंद होगा।
  • प्रत्येक शाखा के लिए सही / गलत स्थिति की गणना करने की लागत। यदि एक शाखा बनाम दूसरे के लिए स्थितियों के परीक्षण की समय लागत वास्तव में अधिक है, तो इससे समय और दक्षता पर महत्वपूर्ण प्रभाव पड़ने की संभावना है। उदाहरण के लिए, एक ऐसी स्थिति पर विचार करें जो गणना करने के लिए 1 समय इकाई लेती है (उदाहरण के लिए, बूलियन चर की स्थिति की जाँच) बनाम एक और शर्त जो गणना करने के लिए दसियों, सैकड़ों, हजारों, या यहां तक ​​कि लाखों समय इकाइयों को लेती है (उदाहरण के लिए, सामग्री की जाँच) डिस्क पर एक फ़ाइल या एक बड़े डेटाबेस के खिलाफ एक जटिल SQL क्वेरी प्रदर्शन)। कोड को मानते हुए हर बार परिस्थितियों की जांच की जाती है, तेज़ स्थिति शायद पहले होनी चाहिए (जब तक कि वे पहले विफल होने वाली अन्य स्थितियों पर निर्भर न हों)।
  • संकलक / दुभाषिया कुछ संकलक (या व्याख्याकार) में एक अन्य प्रकार के अनुकूलन शामिल हो सकते हैं जो प्रदर्शन को प्रभावित कर सकते हैं (और इनमें से कुछ केवल मौजूद हैं यदि कुछ विकल्प संकलन और / या निष्पादन के दौरान चुने गए हैं)। इसलिए जब तक आप दो संकलनों को बेंच रहे हैं और एक ही सिस्टम पर समान कोड के समान कोड का निष्पादन कर रहे हैं, ठीक उसी कंपाइलर का उपयोग कर रहे हैं, जहां केवल अंतर ही शाखाओं का प्रश्न है, तो आपको कंपाइलर वेरिएशन के लिए कुछ लेवे देने होंगे।
  • ऑपरेटिंग सिस्टम / हार्डवेयर luk32 और यक्क द्वारा उल्लिखित के अनुसार, विभिन्न सीपीयू की अपनी अनुकूलन क्षमता होती है (जैसा कि ऑपरेटिंग सिस्टम करते हैं)। तो बेंचमार्क फिर से यहां भिन्नता के लिए अतिसंवेदनशील हैं।
  • कोड ब्लॉक निष्पादन की आवृत्ति यदि ब्लॉक में शाखाएं शामिल हैं, तो शायद ही कभी एक्सेस किया जाता है (उदाहरण के लिए, केवल एक बार स्टार्टअप के दौरान), तो यह संभवतः बहुत कम मायने रखता है कि आप किस क्रम में शाखाएं डालते हैं। दूसरी ओर, यदि आपका कोड आपके कोड के एक महत्वपूर्ण हिस्से के दौरान इस कोड ब्लॉक पर दूर जा रहा है, तो ऑर्डर करना बहुत मायने रखता है (बेंचमार्क पर निर्भर करता है)।

कुछ के लिए जानने का एकमात्र तरीका अपने विशिष्ट मामले को बेंचमार्क करना है, अधिमानतः एक सिस्टम पर (या इसके समान) इच्छित प्रणाली जिस पर कोड अंततः चलेगा। यदि यह अलग-अलग हार्डवेयर, ऑपरेटिंग सिस्टम, आदि के साथ अलग-अलग प्रणालियों के एक सेट पर चलाने का इरादा है, तो यह कई रूपों में बेंचमार्क करने के लिए एक अच्छा विचार है जो यह देखना सबसे अच्छा है। यह भी एक अच्छा विचार हो सकता है कि कोड को एक प्रकार के सिस्टम पर एक आदेश के साथ संकलित किया जाए और दूसरे प्रकार के सिस्टम पर एक अन्य आदेश दिया जाए।

अंगूठे का मेरा व्यक्तिगत नियम (ज्यादातर मामलों में, बेंचमार्क की अनुपस्थिति में) इसके आधार पर ऑर्डर करना है:

  1. ऐसी परिस्थितियां जो पूर्व स्थितियों के परिणाम पर निर्भर करती हैं,
  2. हालत की गणना की लागत, फिर
  3. प्रत्येक शाखा की सापेक्ष संभावना।

13

जिस तरह से मैं आमतौर पर उच्च-प्रदर्शन कोड के लिए इसे हल करता हूं वह उस क्रम को बनाए रखता है जो सबसे पठनीय है, लेकिन संकलक को संकेत प्रदान करता है। यहाँ लिनक्स कर्नेल से एक उदाहरण दिया गया है :

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()मैक्रोज़ वास्तव में पठनीयता को इंगित करने में मदद करते हैं कि सामान्य मामला क्या है और अपवाद क्या है।

उन मैक्रोज़ का लिनक्स कार्यान्वयन जीसीसी विशिष्ट विशेषताओं का उपयोग करता है । ऐसा लगता है कि क्लैंग और इंटेल सी कंपाइलर एक ही सिंटैक्स का समर्थन करते हैं, लेकिन MSVC में ऐसी कोई सुविधा नहीं है


4
यह अधिक उपयोगी होगा यदि आप बता सकते हैं कि कैसे likely()और unlikely()मैक्रोज़ परिभाषित किए गए हैं, और संगत संकलक सुविधा के बारे में कुछ जानकारी शामिल करें।
नैट एल्ड्रेडज

1
AFAIK, ये संकेत "केवल" कोड ब्लॉकों की मेमोरी लेआउट को बदलते हैं और हां या नहीं में एक छलांग लगेगी। यह स्मृति पृष्ठ पढ़ने के लिए आवश्यकता (या इसके अभाव) के लिए उदाहरण के लिए प्रदर्शन लाभ हो सकता है। लेकिन यह उस क्रम को पुनर्व्यवस्थित नहीं करता है, जिसमें किसी और की सूची के भीतर स्थितियों का मूल्यांकन किया जाता है
हेगन वॉन एटिजन

@HagenvonEitzen Hmm हाँ, यह एक अच्छा बिंदु है, यह else ifसंकलक को प्रभावित नहीं कर सकता है यदि कंपाइलर यह जानने के लिए पर्याप्त स्मार्ट नहीं है कि स्थितियां परस्पर अनन्य हैं।
जपा

7

यह आपके कंपाइलर और आपके द्वारा संकलित प्लेटफॉर्म पर भी निर्भर करता है।

सिद्धांत रूप में, सबसे अधिक संभावना स्थिति को नियंत्रण कूद को यथासंभव कम करना चाहिए।

आमतौर पर सबसे संभावित स्थिति पहले होनी चाहिए:

if (most_likely) {
     // most likely instructions
} else 

सबसे लोकप्रिय एएसएम सशर्त शाखाओं पर आधारित हैं जो स्थिति के सही होने पर कूदते हैं । उस C कोड का ऐसे छद्म asm में अनुवाद करने की संभावना होगी:

jump to ELSE if not(most_likely)
// most likely instructions
jump to end
ELSE:

ऐसा इसलिए है क्योंकि जंपर्स सीपीयू को निष्पादन पाइपलाइन और स्टाल को रद्द कर देते हैं क्योंकि प्रोग्राम काउंटर बदल गया (आर्किटेक्चर के लिए जो पाइपलाइनों का समर्थन करते हैं जो वास्तव में आम हैं)। फिर यह संकलक के बारे में है, जो नियंत्रण को कम छलांग लगाने के लिए सांख्यिकीय रूप से सबसे संभवतया स्थिति होने के बारे में कुछ परिष्कृत अनुकूलन लागू कर सकता है या नहीं कर सकता है।


2
आपने कहा था कि सशर्त शाखा तब होती है जब स्थिति सही होती है, लेकिन "छद्म असम" उदाहरण इसके विपरीत दिखाता है। इसके अलावा, यह नहीं कहा जा सकता है कि सशर्त कूदता है (बहुत कम सभी कूदता है) पाइपलाइन को रोक देता है क्योंकि आधुनिक सीपीयू में आमतौर पर शाखा भविष्यवाणी होती है। वास्तव में, यदि शाखा को ले जाने की भविष्यवाणी की जाती है, लेकिन तब नहीं ली जाती है, तो पाइपलाइन ठप हो जाएगी। मैं अभी भी संभाव्यता के अवरोही क्रम में स्थितियों को क्रमबद्ध करने की कोशिश करूंगा, लेकिन संकलक और सीपीयू इसे अत्यधिक कार्यान्वयन पर निर्भर करते हैं।
अर्ने वोगेल

1
मैं "नहीं (सबसे ज्यादा)" लगाता हूं, तो अगर सबसे ज्यादा सच है तो बिना कूद के नियंत्रण हो जाएगा।
NoImaginationGuy

1
"सबसे लोकप्रिय एएसएम उन सशर्त शाखाओं पर आधारित हैं जो स्थिति सही होने पर कूदते हैं" .. जो कि आईएसएएस होगा? यह निश्चित रूप से x86 के लिए और न ही ARM के लिए सही नहीं है। बुनियादी एआरएम सीपीयू के लिए नरक (और बहुत प्राचीन x86 वाले, यहां तक ​​कि जटिल बीपीएस के लिए वे आमतौर पर अभी भी उस धारणा के साथ शुरू करते हैं और फिर अनुकूलित करते हैं) शाखा के भविष्यवक्ता मानते हैं कि आगे की शाखा नहीं ली जाती है और पीछे की शाखाएं हमेशा होती हैं, इसलिए दावे के विपरीत सच हैं।
वू

1
संकलक मैंने कोशिश की ज्यादातर मैंने एक सरल परीक्षण के लिए ऊपर वर्णित दृष्टिकोण का उपयोग किया। ध्यान दें कि clangवास्तव में test2और के लिए एक अलग दृष्टिकोण लिया test3: क्योंकि यह दर्शाता है कि एक < 0या == 0परीक्षण के झूठे होने की संभावना है, इसने दोनों मार्गों पर शेष फ़ंक्शन को क्लोन करने का निर्णय लिया, इसलिए यह condition == falseपथ के माध्यम से गिरावट करने में सक्षम है । यह केवल इसलिए संभव है क्योंकि फ़ंक्शन का शेष छोटा है: test4मैंने एक और ऑपरेशन जोड़ा है और यह उस दृष्टिकोण पर वापस आ गया है जो मैंने ऊपर उल्लिखित किया था।
BeeOnRope

1
@ArneVogel - सही ढंग से भविष्यवाणी की गई शाखाओं ने आधुनिक सीपीयू पर पाइपलाइन को पूरी तरह से रोक नहीं दिया है, लेकिन वे अभी भी अक्सर नहीं लेने की तुलना में काफी खराब हैं: (1) उनका मतलब है कि नियंत्रण प्रवाह सन्निहित नहीं है, इसलिए शेष निर्देशों के बाद jmpनहीं उपयोगी इसलिए भ्रूण / डिकोड बैंडविड्थ व्यर्थ है (2) यहां तक ​​कि भविष्यवाणी के साथ आधुनिक बड़े कोर केवल एक चक्र प्रति चक्र करते हैं इसलिए यह 1 ली गई शाखा / चक्र की एक कठिन सीमा डालता है (OTOH आधुनिक इंटेल 2 नहीं लिया / चक्र कर सकता है) (3) ) ब्रांच की भविष्यवाणी के लिए लगातार शाखाओं से निपटना और तेज + धीमी भविष्यवाणियों के मामले में ...
BeeOnRope

6

मैंने लाइक 32 कोड का उपयोग करके अपनी मशीन पर परीक्षण को फिर से चलाने का फैसला किया। मुझे अपनी विंडोज़ या कंपाइलर सोच के कारण इसे बदलना पड़ा है

mingw32-g ++। exe -O3 -Wall -std = c ++ 11 -fexception -g

vector<int> rand_vec(10000000);

GCC ने दोनों मूल कोडों में समान परिवर्तन किया है।

ध्यान दें कि केवल दो पहली स्थितियों का परीक्षण किया जाता है क्योंकि तीसरा हमेशा सही होना चाहिए, जीसीसी एक प्रकार का शर्लक है।

उलटना

.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 संयोजनों की कोशिश की, शीर्ष 2 मूल रिवर्स और सॉर्ट किए गए हैं। उच्च है = = 95, निम्न <20 है, मध्य प्रत्येक 10000000 पुनरावृत्तियों के साथ 20-94 है।

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 चक्र गलत अर्थदंड लगाया गया है और प्रत्येक चेक को जारी करने के लिए 1 चक्र लगता है (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 नए बाइट्स को सभी नए इंटेल सीपीयू पर गठबंधन किया जाना चाहिए, लेकिन इस मामले में नहीं है।


4

आपको जो भी तार्किक क्रम पसंद हो, उसमें उन्हें डालें। निश्चित रूप से, शाखा धीमी हो सकती है, लेकिन आपके कंप्यूटर द्वारा जो काम किया जा रहा है, उसमें शाखाओं का बहुमत नहीं होना चाहिए।

यदि आप कोड के प्रदर्शन के महत्वपूर्ण हिस्से पर काम कर रहे हैं, तो निश्चित रूप से तार्किक क्रम, प्रोफ़ाइल निर्देशित अनुकूलन और अन्य तकनीकों का उपयोग करें, लेकिन सामान्य कोड के लिए, मुझे लगता है कि यह वास्तव में एक शैलीगत पसंद है।


6
शाखा पूर्वानुमान विफलताएं महंगी हैं। माइक्रोबैनचर्च में, उनकी लागत होती है , क्योंकि x86s में शाखा भविष्यवक्ताओं की एक बड़ी तालिका होती है। सीपीयू में एक ही स्थिति में एक बेहतर लूप होने से आप बेहतर जानते हैं कि कौन सा सबसे अधिक संभावना है। लेकिन अगर आपके पास अपने कोड में सभी शाखाएं हैं, तो आप अपनी शाखा की भविष्यवाणी कैश को स्लॉट्स से चला सकते हैं, और सीपीयू मान लेता है कि जो भी डिफ़ॉल्ट है। यह जानकर कि क्या डिफ़ॉल्ट अनुमान है, अपने कोड आधार पर सभी चक्रों को बचा सकता है।
यक्क - एडम नेवरामोंट

@ यक जैक का जवाब यहाँ एकमात्र सही है। यदि आपके कंपाइलर उस अनुकूलन को करने में सक्षम हैं, तो अनुकूलन क्षमता को कम न करें। यदि आपके कंपाइलर आपके लिए ऐसा करते हैं, तो आप लगातार फोल्डिंग, डेड कोड एलिमिनेशन, लूप अनरोलिंग या कोई अन्य ऑप्टिमाइज़ेशन नहीं करेंगे? अपना कोड लिखें, प्रोफ़ाइल निर्देशित अनुकूलन का उपयोग करें (जो इस मुद्दे को हल करने के लिए डिज़ाइन है क्योंकि कोडर अनुमान लगाते हैं) और फिर देखें कि आपका कंपाइलर इसे अनुकूलित करता है या नहीं। अंत में आप किसी भी तरह से प्रदर्शन महत्वपूर्ण कोड में कोई भी शाखा नहीं रखना चाहते हैं।
क्रिस्टोफ डाइगलमैन

@Christoph मैं उस कोड को शामिल नहीं करूंगा जिसे मैं मरा हुआ जानता था। मैं का प्रयोग करेंगे नहीं i++जब ++iकरना होगा, क्योंकि मुझे पता है कि कर रहा हूँ i++कुछ iterators के लिए करने के लिए नीचे अनुकूलन करने के लिए कठिन है ++iऔर अंतर (मेरे लिए) बात नहीं करता है। यह निराशावाद से बचने के बारे में है; डिफ़ॉल्ट आदत के रूप में सबसे अधिक संभावना वाले ब्लॉक को पहले ध्यान देने योग्य पठनीयता में कमी (और वास्तव में मदद कर सकता है!) का कारण नहीं होगा, जबकि शाखा-पूर्व अनुकूल (और इस प्रकार आप एक समान छोटे प्रदर्शन को बढ़ावा देने वाले कोड को पुनः प्राप्त नहीं किया जा सकता है)। बाद में माइक्रो ऑप्टिमाइज़ेशन)
यक्क - एडम नेवरामोंट

3

यदि आप पहले से ही इफ-स्टेटमेंट की सापेक्ष संभावना को जानते हैं, तो प्रदर्शन उद्देश्य के लिए सॉर्ट किए गए तरीके का उपयोग करना बेहतर होगा, क्योंकि यह केवल एक शर्त (सही एक) की जांच करेगा।

एक अनसुने तरीके से कंपाइलर अनावश्यक रूप से सभी स्थितियों की जाँच करेगा और समय लेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.