c++ - क्या मौजूदा x86 आर्किटेक्चर गैर-अस्थायी भार("सामान्य" मेमोरी से) का समर्थन करते हैं?




caching prefetch (2)

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

माप Xeon E5 2680v3 हैसवेल प्रकार CPU पर 32 केआईबी एल 1 डेटा कैश के साथ किए गए थे। इसलिए, सभी मामलों में, b 1 कैश में लगाया गया है। हालांकि, लगभग 16 किलोग्राम b मेमोरी पदचिह्न द्वारा मिस की संख्या में काफी वृद्धि हुई है। इसकी अपेक्षा की जा सकती है क्योंकि a और b दोनों के भार इस बिंदु पर b की शुरुआत से कैश लाइनों के अमान्यता का कारण बनते हैं।

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

मेरा बेंचमार्क कोड निम्नानुसार है (संस्करण w / o गैर-अस्थायी prefetching दिखाया गया है):

int main(int argc, char* argv[])
{
   uint64_t* a;
   const uint64_t a_bytes = 64 * 1024 * 1024;
   const uint64_t a_count = a_bytes / sizeof(uint64_t);
   posix_memalign((void**)(&a), 64, a_bytes);

   uint64_t* b;
   const uint64_t b_bytes = atol(argv[1]) * 1024;
   const uint64_t b_count = b_bytes / sizeof(uint64_t);
   posix_memalign((void**)(&b), 64, b_bytes);

   __m256i ones = _mm256_set1_epi64x(1UL);
   for (long i = 0; i < a_count; i += 4)
       _mm256_stream_si256((__m256i*)(a + i), ones);

   // load b into L1 cache
   for (long i = 0; i < b_count; i++)
       b[i] = 0;

   int papi_events[1] = { PAPI_L1_DCM };
   long long papi_values[1];
   PAPI_start_counters(papi_events, 1);

   uint64_t* a_ptr = a;
   const uint64_t* a_ptr_end = a + a_count;
   uint64_t* b_ptr = b;
   const uint64_t* b_ptr_end = b + b_count;

   while (a_ptr < a_ptr_end) {
#ifndef NTLOAD
      __m256i aa = _mm256_load_si256((__m256i*)a_ptr);
#else
      __m256i aa = _mm256_stream_load_si256((__m256i*)a_ptr);
#endif
      __m256i bb = _mm256_load_si256((__m256i*)b_ptr);
      bb = _mm256_add_epi64(aa, bb);
      _mm256_store_si256((__m256i*)b_ptr, bb);

      a_ptr += 4;
      b_ptr += 4;
      if (b_ptr >= b_ptr_end)
         b_ptr = b;
   }

   PAPI_stop_counters(papi_values, 1);
   std::cout << "L1 cache misses: " << papi_values[0] << std::endl;

   free(a);
   free(b);
}

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

अद्यतन करें

मैंने इंटेल ज़ीऑन फाई केएनसी पर एक समान प्रयोग किया है, जबकि एल 1 मिस के बजाए रनटाइम को मापने के दौरान (मुझे विश्वसनीय तरीके से उन्हें मापने का तरीका नहीं मिला है; पीएपीआई और वीट्यून ने अजीब मेट्रिक्स दिए हैं।) परिणाम यहां हैं:

नारंगी वक्र सामान्य भार का प्रतिनिधित्व करता है और इसकी अपेक्षित आकार है। नीला वक्र निर्देश उपसर्ग में सेट तथा-कॉल बेदखल संकेत (ईएच) के साथ लोड का प्रतिनिधित्व करता है और ग्रे वक्र एक ऐसे मामले का प्रतिनिधित्व करता है जहां प्रत्येक कैश लाइन को मैन्युअल रूप से बेदखल कर दिया गया था; केएनसी द्वारा सक्षम इन दोनों चालों ने स्पष्ट रूप से काम किया क्योंकि हम 16 से अधिक b लिए चाहते थे। मापा पाश का कोड इस प्रकार है:

while (a_ptr < a_ptr_end) {
#ifdef NTLOAD
   __m512i aa = _mm512_extload_epi64((__m512i*)a_ptr,
      _MM_UPCONV_EPI64_NONE, _MM_BROADCAST64_NONE, _MM_HINT_NT);
#else
   __m512i aa = _mm512_load_epi64((__m512i*)a_ptr);
#endif
   __m512i bb = _mm512_load_epi64((__m512i*)b_ptr);
   bb = _mm512_or_epi64(aa, bb);
   _mm512_store_epi64((__m512i*)b_ptr, bb);

#ifdef EVICT
   _mm_clevict(a_ptr, _MM_HINT_T0);
#endif

   a_ptr += 8;
   b_ptr += 8;
   if (b_ptr >= b_ptr_end)
       b_ptr = b;
}

अद्यतन 2

icpc फाई पर, icpc सामान्य-लोड संस्करण (नारंगी वक्र) के लिए a_ptr जो a_ptr लिए prefetching:

400e93:       62 d1 78 08 18 4c 24    vprefetch0 [r12+0x80]

जब मैं मैन्युअल रूप से (निष्पादन योग्य हेक्स-संपादन करके) इसे संशोधित करता हूं:

400e93:       62 d1 78 08 18 44 24    vprefetchnta [r12+0x80]

मुझे वांछित resutls मिल गया, नीले / ग्रे वक्र से भी बेहतर। हालांकि, मैं लूप से पहले #pragma prefetch a_ptr:_MM_HINT_NTA का उपयोग करके, मेरे लिए गैर-अस्थायी prefetchnig उत्पन्न करने के लिए संकलक को मजबूर करने में सक्षम नहीं था :(


मैं अपने स्वयं के प्रश्न का उत्तर देता हूं क्योंकि मुझे इंटेल डेवलपर फोरम से निम्न पोस्ट मिली है, जो मेरे लिए समझ में आता है। यह जॉन मैककल्पिन द्वारा लिखा गया था:

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

इन दोनों दृष्टिकोणों में एक छोटी संख्या में सरणी से अधिक परिचालन करने वाले मामलों में प्रदर्शन में गिरावट का जोखिम होता है, और हाइपर थ्रेडिंग पर विचार किए जाने पर "गॉथचास" के बिना इसे लागू करना अधिक कठिन होता है।

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

हैसवेल से शुरू, कोर एक चक्र में 64 बाइट्स पढ़ सकता है (2 256-बिट गठबंधन एवीएक्स पढ़ता है), इसलिए अनपेक्षित साइड इफेक्ट्स का संपर्क भी कम हो जाता है।

केएनएल से शुरू होने पर, पूर्ण-कैश-लाइन (गठबंधन) लोड "स्वाभाविक रूप से" परमाणु होना चाहिए, क्योंकि एल 1 डेटा कैश से कोर में स्थानान्तरण पूर्ण कैश लाइन हैं और सभी डेटा लक्ष्य AVX-512 रजिस्टर में रखा गया है। (इसका मतलब यह नहीं है कि इंटेल कार्यान्वयन में परमाणुता की गारंटी देता है! हमारे पास भयानक कोने के मामलों में दृश्यता नहीं है जो डिजाइनरों के लिए खाते हैं, लेकिन यह निष्कर्ष निकालना उचित है कि अधिकांश समय 512-बिट भार संरेखित होगा परमाणु रूप से।) इस "प्राकृतिक" 64-बाइट परमाणुता के साथ, "अस्थायी" लोड के कारण कैश प्रदूषण को कम करने के लिए अतीत में उपयोग की जाने वाली कुछ चालें एक और रूप के लायक हो सकती हैं ....

MOVNTDQA निर्देश मुख्य रूप से पता श्रेणी से पढ़ने के लिए है, जिसे "लिखें-संयोजन" (डब्ल्यूसी) के रूप में मैप किया गया है, और "सिस्टम-बैक" (डब्ल्यूबी) मैप किए गए सामान्य सिस्टम मेमोरी से पढ़ने के लिए नहीं है। एसडब्ल्यूडीएम के वॉल्यूम 2 ​​में वर्णन का कहना है कि एक कार्यान्वयन डब्लूबी क्षेत्रों के लिए MOVNTDQA के साथ कुछ विशेष कर सकता है, लेकिन डब्ल्यूसी मेमोरी प्रकार के व्यवहार पर जोर दिया जाता है।

"लिखें-संयोजन" मेमोरी प्रकार लगभग "वास्तविक" स्मृति के लिए कभी भी उपयोग नहीं किया जाता है --- इसका उपयोग लगभग मेमोरी-मैप किए गए आईओ क्षेत्रों के लिए लगभग विशेष रूप से किया जाता है।

पूरी पोस्ट के लिए यहां देखें: software.intel.com/en-us/forums/intel-isa-extensions/topic/…


विशेष रूप से शीर्षक प्रश्न का उत्तर देने के लिए:

हां , हाल ही में 1 मुख्यधारा के इंटेल सीपीयू सामान्य 2 मेमोरी पर गैर-अस्थायी भार का समर्थन करते हैं - लेकिन गैर-अस्थायी प्रीफेच निर्देशों के माध्यम से केवल "अप्रत्यक्ष रूप से" movntdqa जैसे गैर-अस्थायी लोड निर्देशों का उपयोग करने के बजाय। यह गैर-अस्थायी स्टोर के विपरीत है जहां आप सीधे संबंधित गैर-अस्थायी स्टोर निर्देश 3 का उपयोग कर सकते हैं।

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

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

Skylake ग्राहक

इस जवाब में परीक्षणों के आधार पर ऐसा लगता है कि prefetchnta स्काइलेक के लिए व्यवहार सामान्य रूप से एल 1 कैश में लाने के लिए है, एल 2 को पूरी तरह से छोड़ने के लिए, और एल 3 कैश में सीमित तरीके से लाता है (शायद केवल 1 या 2 तरीकों से nta लिए उपलब्ध एल 3 की कुल राशि सीमित है)।

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

Skylake सर्वर

एकमात्र हालिया इंटेल चिप को अलग-अलग व्यवहार के लिए जाना जाता है स्काइलेक सर्वर (स्काइलेक-एक्स, स्काइलेक-एसपी और कुछ अन्य लाइनों में उपयोग किया जाता है)। इसमें काफी बदलाव आया है L2 और L3 आर्किटेक्चर, और एल 3 अब अधिक बड़े एल 2 सहित शामिल नहीं है। इस चिप के लिए, ऐसा लगता है कि prefetchnta एल 2 और एल 3 कैश दोनों को छोड़ देता है, इसलिए इस आर्किटेक्चर कैश प्रदूषण पर एल 1 तक ही सीमित है।

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

1 यहां हाल ही में पिछले दशक में कुछ भी मतलब है, लेकिन मेरा मतलब यह नहीं है कि पहले हार्डवेयर गैर-अस्थायी प्रीफेच का समर्थन नहीं करता था: यह संभव है कि समर्थन prefetchnta के परिचय पर वापस चला जाता है लेकिन मैं नहीं करता हार्डवेयर को जांचने के लिए है और उस पर जानकारी का मौजूदा विश्वसनीय स्रोत नहीं मिल रहा है।

2 सामान्य यहां केवल डब्लूबीबी (फीडबैक) मेमोरी का मतलब है, जो उस समय के भारी बहुमत के आवेदन स्तर पर काम करने वाली स्मृति है।

3 विशेष रूप से, एनटी स्टोर निर्देश सामान्य उद्देश्य रजिस्टरों और movntd* और movntp* परिवारों के लिए movntp* सिम रजिस्टरों के लिए।





prefetch