मैं यह समझने की कोशिश कर रहा हूं कि सीपीयू में शाखा भविष्यवाणी इकाई कैसे काम करती है।

मैंने papi और linux के perf-events का भी उपयोग किया है, लेकिन वे दोनों सटीक परिणाम नहीं देते हैं (मेरे मामले के लिए)।

यह मेरा कोड है:

void func(int* arr, int sequence_len){
  for(int i = 0; i < sequence_len; i++){
      // region starts
      if(arr[i]){
          do_sth();
      }
      // region ends
  }
}

मेरी सरणी में 0 और 1 है। इसका आकार sequence_len के साथ एक पैटर्न है। उदाहरण के लिए, यदि मेरा आकार 8 है, तो उसका पैटर्न 0 1 0 1 0 0 1 1 या ऐसा ही कुछ है।

परीक्षण 1:

मैं यह समझने की कोशिश कर रहा हूं कि सीपीयू उन शाखाओं की भविष्यवाणी कैसे करता है। इसलिए, मैंने पपी का उपयोग किया है और शाखा की गलत भविष्यवाणी के लिए प्रदर्शन काउंटर स्थापित किया है (मुझे पता है कि यह अप्रत्यक्ष शाखाओं की भी गणना करता है)।

int func(){
  papi_read(r1);
  for(){
    //... same as above
  }
  papi_read(r2);
  return r2-r1;
}

int main(){
   init_papi();
   for(int i = 0; i < 10; i++)
     res[i] = func();

   print(res[i]);
}

मैं आउटपुट के रूप में जो देखता हूं वह है (200 की अनुक्रम लंबाई के लिए)

100 #iter1
40  #iter2
10  #iter3
3
0
0
#...

तो, सबसे पहले, सीपीयू आँख बंद करके अनुक्रम की भविष्यवाणी करता है, केवल आधे समय में सफलता। अगले पुनरावृत्तियों में, सीपीयू बेहतर और बेहतर भविष्यवाणी कर सकता है। कुछ मात्रा में पुनरावृत्तियों के बाद, सीपीयू पूरी तरह से अनुमान लगा सकता है।

परीक्षण 2

मैं देखना चाहता हूं कि सीपीयू किस एरे इंडेक्स पर गलत भविष्यवाणी करता है।

int* func(){
  int* results;
  for(){
    papi_read(r1);
    if(arr[i])
        do_sth();   
    papi_read(r2);
    res[i] = r2-r1;
  }
  return res;
}

int main(){
   init_papi();
   for(int i = 0; i < 10; i++)
     res[i] = func();

   print(res[i]);
}

अपेक्षित परिणाम:

#1st iteration, 0 means no mispred, 1 means mispred
1 0 0 1 1 0 0 0 1 1 0... # total of 200 results
Mispred: 100/200
#2nd iteration
0 0 0 0 1 0 0 0 1 0 0... # total of 200 results
Mispred: 40/200 # it learned from previous iteration
#3rd iteration
0 0 0 0 0 0 0 0 1 0 0... # total of 200 results
Mispred: 10/200 # continues to learn
#...

प्राप्त परिणाम:

#1st iteration
1 0 0 1 1 0 0 0 1 1 0... # total of 200 results
Mispred: 100/200
#2nd iteration
1 0 0 0 1 1 0 1 0 0 0... # total of 200 results
Mispred: 100/200 # it DID NOT learn from previous iteration
#3rd iteration
0 1 0 1 0 1 0 1 1 0 0... # total of 200 results
Mispred: 100/200 # NO LEARNING
#...

मेरा अवलोकन

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

मेरी व्याख्या

मैं अनुक्रम लंबाई के रूप में 200 दे रहा हूं। सीपीयू में एक छोटा शाखा भविष्यवक्ता होता है, जैसे कि इंटेल में 2-3 बिट संतृप्त काउंटर, और एक बड़ा वैश्विक शाखा भविष्यवक्ता। जब मैं लूप के बाहर मापता हूं, तो मैं माप के लिए कम शोर का परिचय देता हूं। कम शोर से मेरा मतलब papi कॉल्स से है।

इस बारे में सोचें: लूप माप के बाहर

वैश्विक इतिहास है: papi_start, branch_outcome1, branch_outcome2, branch_outcome3, ..., papi_end, papi_start (2nd loop of main iteration), branch_outcome1, ...

तो, शाखा भविष्यवक्ता किसी तरह उसी शाखा में पैटर्न ढूंढता है।

हालाँकि, अगर मैं एकल शाखा निर्देश को मापने की कोशिश करता हूँ तो वैश्विक इतिहास है: papi_start, branchoutcome1, papiend, papistart, branchoutcome2, papiend...

इसलिए, मैं वैश्विक इतिहास में अधिक से अधिक शाखाओं का परिचय दे रहा हूं। मुझे लगता है कि वैश्विक इतिहास में कई शाखा प्रविष्टियां नहीं हो सकती हैं और इसलिए, यदि कथन (शाखा) वांछित में कोई सहसंबंध/पैटर्न नहीं ढूंढ सकता है।

परिणामस्वरूप

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

इसलिए मुझे बेहतर माप की जरूरत है। मैंने linux perf-event की कोशिश की है, लेकिन यह ioctl कॉल करता है, जो एक सिस्टम कॉल है और मैं सिस्टम कॉल के साथ वैश्विक इतिहास को प्रदूषित करता हूं, और इसलिए, एक अच्छा माप नहीं है।

मैंने वह rdpmc और rdmsr निर्देश पढ़ लिए हैं और मुझे लगता है कि चूंकि वे केवल निर्देश हैं, मैं वैश्विक इतिहास को प्रदूषित नहीं करूंगा, और मैं एक समय में एक शाखा निर्देश को माप सकता हूं।

हालांकि, मुझे इस बारे में कोई जानकारी नहीं है कि मैं यह कैसे कर सकता हूं। मेरे पास एएमडी 3600 सीपीयू है। ये वे लिंक हैं जो मुझे ऑनलाइन मिले लेकिन मैं यह नहीं समझ पाया कि यह कैसे किया जाए। इसके अलावा, क्या मुझे कुछ याद आ रहा है?

इंटेल rdpmc

एएमडी प्रदर्शन मैनुअल

8
user12527223 17 फरवरी 2020, 17:51
एक नंगे धातु सॉफ्टवेयर पर कोशिश क्यों नहीं कर रहे हैं? उदाहरण के लिए एआरएम माइक्रोकंट्रोलर पर। व्यवहार अधिक अनुमानित और डिबग करने में आसान होगा क्योंकि कोई OS नहीं है?
 – 
The_Average_Engineer
17 फरवरी 2020, 18:27
एआरएम कॉर्टेक्स पर शाखा भविष्यवाणी को मापने के बारे में यहां एक अच्छा लेख है: community.arm.com/developer/ip-products/processors/b/…
 – 
The_Average_Engineer
17 फरवरी 2020, 18:33
खैर, मैं एएमडी प्रोसेसर को मापना चाहता हूं। मुझे लगता है कि आपका लिंक मेरे प्रश्न का एक मूल्यवान उत्तर प्रदान नहीं करता है। लेकिन मैं इसे केवल नई चीजें सीखने के लिए देखूंगा। @The_Average_Engineer
 – 
user12527223
17 फरवरी 2020, 18:39
1
@The_Average_Engineer: x86 CPU वास्तविक मोड में बूट होते हैं, और मदरबोर्ड में हमेशा फर्मवेयर बिल्ट-इन होता है जो या तो UEFI एप्लिकेशन या लीगेसी BIOS बूट सेक्टर को लोड करता है। यह एआरएम बोर्ड की तरह नहीं है जहां आप मूल रूप से फर्मवेयर को फ्लैश में लिख रहे हैं। मुझे नहीं लगता कि नंगे धातु (या यूईएफआई के तहत भी चल रहा है) एक बहुत ही उपयोगी सुझाव है। कम से कम एक UEFI एप्लिकेशन को सामान्य 64-बिट कोड चलाने के लिए osdev बकवास (जैसे GDT और पेज टेबल सेट करना) का एक गुच्छा नहीं करना होगा, और किसी फ़ाइल में परिणाम सहेजने के लिए UEFI फ़ंक्शन का उपयोग कर सकता है। लेकिन आपके पास डीबगर या कुछ भी नहीं होगा।
 – 
Peter Cordes
18 फरवरी 2020, 02:50

2 जवाब

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

यदि आपका कर्नेल "यूजर-मोड RDPMC" (CR4.PCE=1) का समर्थन करता है, तो आप एक निर्देश के साथ एक प्रदर्शन काउंटर पढ़ सकते हैं। उदाहरण https://github.com/jdmccalpin/low-overhead-timers में उपलब्ध हैं। .

यहां तक ​​​​कि जब माप कोड को मूल RDPMC निर्देश (और परिणामों को बचाने के लिए आसपास के कोड) तक सीमित किया जाता है, तो माप प्रोसेसर पाइपलाइन के लिए विघटनकारी होते हैं। RDPMC एक माइक्रोकोडेड निर्देश है। रेजेन कोर पर, निर्देश 20 माइक्रो-ऑप्स निष्पादित करता है और प्रति 20 चक्रों में एक निर्देश का थ्रूपुट होता है। (संदर्भ: https://www.agner.org/optimize/instruction_tables.pdf)

बारीक ग्रैन्युलैरिटी पर कोई भी माप चुनौतीपूर्ण है क्योंकि आधुनिक प्रोसेसर की आउट-ऑफ-ऑर्डर क्षमताएं उपयोगकर्ता कोड के साथ इस तरह से इंटरैक्ट करती हैं जो खराब तरीके से प्रलेखित हैं और अनुमान लगाना मुश्किल है। इस विषय पर अधिक नोट्स (एएमडी प्रोसेसर के लिए भी प्रासंगिक) http://sites.utexas.edu/jdm4372/2018/07/23/comments-on-timeing-short-code-sections-on-intel-processors/

5
John D McCalpin 17 फरवरी 2020, 19:58

perf_event_open() दस्तावेज में बताया गया है कि कैसे सही तरीके से इस्तेमाल किया जाए rdpmc उस इंटरफ़ेस के माध्यम से बनाए गए ईवेंट के साथ। @ जॉनडीएमकैल्पिन के उत्तर में वर्णित दृष्टिकोण भी काम करता है, लेकिन यह सीधे इवेंट कंट्रोल रजिस्टरों की प्रोग्रामिंग पर आधारित है। हार्डवेयर ईवेंट के एक सेट को देखते हुए, उपलब्ध हार्डवेयर प्रदर्शन काउंटर पर इन ईवेंट को शेड्यूल करने का तरीका पता लगाना मुश्किल हो सकता है। perf_event सबसिस्टम आपके लिए इस समस्या को हैंडल करता है, जो एक बड़ा फायदा है।

perf_event सबसिस्टम Linux 3.4 के बाद से rdpmc को सपोर्ट करता है।

<linux/perf_event.h> से शुरू होकर, निम्नलिखित कार्य करता है:

  1. type = PERF_TYPE_HARDWARE config = PERF_COUNT_HW_BRANCH_MISSES के काउंटर को पढ़ने के लिए तैयार करने के लिए perf_event_open() करें

    struct perf_event_attr attr ;
    int fd ;
    
    memset(&attr, 0, sizeof(attr)) ;
    
    attr.type   = PERF_TYPE_HARDWARE ;
    attr.config = PERF_COUNT_HW_BRANCH_MISSES;
    attr.size = sizeof(attr) ;        // for completeness
    attr.exclude_kernel = 1 ;         // count user-land events
    
    perf_fd = (int)sys_perf_event_open(&attr, 0, -1, -1, PERF_FLAG_FD_CLOEXEC) ;
                                      // this pid, any cpu, no group_fd
    

    कहां:

    static long
    sys_perf_event_open(struct perf_event_attr* attr,
                                  pid_t pid, int cpu, int group_fd, ulong flags)
    {
      return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags) ;
    }
    
  2. perf_fd को एक mmap पृष्ठ के साथ संबद्ध करें:

    struct perf_event_mmap_page* perf_mm ;
    
    perf_mm = mmap(NULL, page_size, PROT_READ, MAP_SHARED, perf_fd, 0) ;
    

    उदाहरण के लिए पेज_साइज 4096 हो सकता है। इस बफर का उपयोग नमूनों को संग्रहीत करने के लिए किया जाता है। दस्तावेज़ीकरण का "अतिप्रवाह प्रबंधन" अनुभाग देखें।

  3. काउंटर को पढ़ने के लिए आपको perf_mm में कुछ जानकारी को RDPMC निर्देश का उपयोग करके पढ़ने की आवश्यकता है, इस प्रकार:

    uint64_t  offset, count ;
    uint32_t  lock, check, a, d, idx ;
    
    lock = perf_mm->lock ;
    do
      {
        check = lock ;
        __asm__ volatile("":::"memory") ;
        idx = perf_mm->index - 1 ;
        // Check that you're allowed to execute rdpmc. You can do this check once.
        // Check also that the event is currently active.
        // Starting with Linux 3.12, use cap_user_rdpmc.
        if (perf_mm->cap_user_rdpmc && idx) {
           // cap_user_rdpmc cannot change at this point because no code
           // that executes here that changes it. So it's safe.
           __asm__ volatile("\t rdpmc\n" : "=a" (a), "=d" (d) : "c" (idx)) ;
        }
        // In case of signed event counts, you have to use also pmc_width.
        // See the docs.
         offset = perf_mm->offset ;
        __asm__ volatile("":::"memory") ;
        lock = perf_mm->lock ;
      }
    while (lock != check) ;
    
    count = ((uint64_t)d << 32) + a ;
    if (perf_mm->pmc_width != 64)
      {
        // need to sign extend the perf_mm->pmc_width bits of count.
      } ;
    count += offset ;
    

    यदि "प्रारंभ" और "अंत" के बीच धागा बाधित नहीं होता है, तो मुझे लगता है कि हम मान सकते हैं कि perf_mm सामान नहीं बदलेगा। लेकिन अगर यह बाधित होता है, तो कर्नेल इस समय को प्रभावित करने वाले किसी भी बदलाव के लिए perf_mm सामग्री को अपडेट कर सकता है।

  4. नोट: RDPMC निर्देशों के आसपास बहुत बड़ा नहीं है, लेकिन मैं यह सब वापस लेने के साथ प्रयोग कर रहा हूं और देख रहा हूं कि क्या मैं सीधे RDPMC परिणामों का उपयोग कर सकता हूं, बशर्ते कि perf_mm->lock न बदले .

5
Chris Hall 19 मार्च 2020, 18:21
2
एक __rdpmc आंतरिक है, लेकिन जाहिर तौर पर यह gcc6.5 / 7.4 / 8.3 तक छोटी थी; इससे पहले यह ठीक से अस्थिर नहीं था . यदि आपके पास नया GCC है तो आप उसका उपयोग कर सकते हैं; लेकिन मुझे लगता है कि इनलाइन एएसएम ठीक है। आपने rdpmc के आउटपुट के लिए C var छोड़े हैं। आम तौर पर आप "=a"(low_half_result) या कुछ और चाहते हैं। (var_name) भाग को छोड़ना सिंटैक्स त्रुटि है।
 – 
Peter Cordes
18 फरवरी 2020, 03:06
धन्यवाद। "=a" (a), "=d" (d) पर फिक्स्ड।
 – 
Chris Hall
18 फरवरी 2020, 03:13
@ हादी: संपादन के लिए धन्यवाद। क्या रीड लूप में if (pc->cap_user_rdpmc && idx) की जांच करना आवश्यक है? मैंने time_offset आदि का उल्लेख किया है क्योंकि दस्तावेज़ में कोड नमूना यह दिखाने के लिए कि rdpmc का उपयोग कैसे किया जाता है, लेकिन इन उद्देश्यों के लिए ऐसा करना आवश्यक नहीं है। आपने page_size को "उदाहरण के लिए 4096" कहने के लिए बदल दिया है: क्या आपका मतलब है कि यह इस उद्देश्य के लिए 4096 हो सकता है - अर्थात्, rdpmc का उपयोग करके PERF_TYPE_HARDWARE काउंटर पढ़ना? आपने "दस्तावेज़ीकरण" में "ओवरफ़्लो हैंडलिंग" की ओर भी इशारा किया: इस मामले में यह कैसे प्रासंगिक है? अंत में: मैं कैसे बता सकता हूं कि मेरे पास "हस्ताक्षरित घटना गणना" कब है?
 – 
Chris Hall
21 फरवरी 2020, 12:03
1
idx अमान्य है यदि ईवेंट वर्तमान में सक्रिय नहीं है (उदा., बहुसंकेतन के कारण)। यदि आप किसी अमान्य idx से rdpmc का प्रयास करते हैं, तो आप या तो किसी भिन्न घटना का काउंटर पढ़ेंगे या एक अपवाद उत्पन्न होगा। प्रोग्राम की शुरुआत में केवल एक बार cap_user_rdpmc के लिए जांच करना पर्याप्त हो सकता है यदि आप सुनिश्चित रूप से जानते हैं कि बाद में कोई और उपयोगकर्ता-मोड rdpmc को किसी कारण से अक्षम नहीं कर सकता है। उस बफर का उपयोग ईवेंट नमूने रखने के लिए किया जाता है। जब बफ़र गिर जाता है, तो कर्नेल आपके द्वारा बफ़र को संसाधित करने के लिए पंजीकृत फ़ंक्शन को आमंत्रित करता है। दस्तावेज़ीकरण चर्चा करता है कि बफर का उपयोग कैसे किया जाता है।
 – 
Hadi Brais
21 फरवरी 2020, 19:05
2
वे प्रति थ्रेड हैं, लेकिन एक एकल थ्रेड हार्डवेयर काउंटरों की तुलना में अधिक हार्डवेयर ईवेंट शेड्यूल कर सकता है, जो मल्टीप्लेक्सिंग को ट्रिगर करता है। इस प्रकार कुछ ईवेंट सक्षम किए जा सकते हैं लेकिन सक्रिय नहीं। निश्चित रूप से, आप cap_user_rdpmc को हटा सकते हैं यदि आप गारंटी दे सकते हैं कि उपयोगकर्ता-मोड rdpmc इसके निष्पादित होने के समय सक्षम है। अन्यथा, कोड क्रैश हो जाएगा।
 – 
Hadi Brais
21 फरवरी 2020, 20:15