दिलचस्प पोस्ट
पीलाब के साथ एक आंकड़ा कैसे बचा सकता है? Django DB सेटिंग्स 'अयोग्य कॉन्फ़िगर' त्रुटि java.util.NoSuchElementException: कोई रेखा नहीं मिली संकेतक के सरणी / सर के लिए सी पॉन्टर जावास्क्रिप्ट के बिना html और सीएसएस में क्लिक करने योग्य एक संपूर्ण 'div' कैसे करें? प्रतिलिपि कन्स्ट्रक्टर C ++ में संदर्भ द्वारा अपने पैरामीटर को क्यों स्वीकार करना चाहिए? एक बेजियर पथ के साथ छवियों को रखें डेटटाइम, टाइमस्टैम्प और डेटटाइम के बीच परिवर्तित करना C ++ में निजी विधि को कॉल करना सदस्य कन्स्ट्रक्टर और डिस्ट्रिक्टर कॉल के आदेश पेशेवर jQuery आधारित combobox नियंत्रण? अजगर बाध्य और अनबाउंड विधि ऑब्जेक्ट इसका अर्थ यह है कि वैश्विक नामस्थान प्रदूषित होगा? कैसे पता चलेगा कि दो सरणियों के समान मूल्य हैं क्यों "आयात *" खराब है?

एक संयुक्त पाश की तुलना में अलग loops में elementwise जोड़ों में बहुत तेजी क्यों हैं?

मान लें कि a1 b1 , b1 , c1 , और d1 बिंदु हेप मेमोरी और मेरे संख्यात्मक कोड में निम्न कोर लूप है।

 const int n = 100000; for (int j = 0; j < n; j++) { a1[j] += b1[j]; c1[j] += d1[j]; } 

इस पाश को लूप के for किसी अन्य बाहरी माध्यम से 10,000 बार निष्पादित for है। इसे तेज करने के लिए, मैंने कोड को इसमें बदल दिया:

 for (int j = 0; j < n; j++) { a1[j] += b1[j]; } for (int j = 0; j < n; j++) { c1[j] += d1[j]; } 

इंटेल कोर 2 डुओ (x64) पर 32-बिट के लिए पूर्ण अनुकूलन और एसएसई 2 सक्षम एमएस विज़ुअल सी ++ 10.0 पर संकलित किया गया, पहला उदाहरण 5.5 सेकंड लेता है और डबल-लूप का उदाहरण केवल 1.9 सेकंड लेता है। मेरा प्रश्न यह है कि: (कृपया नीचे दिए गए मेरे पुन: संदर्भित प्रश्न को देखें)

पुनश्च: मुझे यकीन नहीं है, अगर यह मदद करता है:

पहले लूप के लिए असहयोग करना मूल रूप से इस तरह दिखता है (इस ब्लॉक को पूर्ण कार्यक्रम में लगभग पांच बार दोहराया जाता है):

 movsd xmm0,mmword ptr [edx+18h] addsd xmm0,mmword ptr [ecx+20h] movsd mmword ptr [ecx+20h],xmm0 movsd xmm0,mmword ptr [esi+10h] addsd xmm0,mmword ptr [eax+30h] movsd mmword ptr [eax+30h],xmm0 movsd xmm0,mmword ptr [edx+20h] addsd xmm0,mmword ptr [ecx+28h] movsd mmword ptr [ecx+28h],xmm0 movsd xmm0,mmword ptr [esi+18h] addsd xmm0,mmword ptr [eax+38h] 

डबल लूप उदाहरण के प्रत्येक लूप से इस कोड का उत्पादन होता है (निम्न ब्लॉक को तीन बार दोहराया जाता है):

 addsd xmm0,mmword ptr [eax+28h] movsd mmword ptr [eax+28h],xmm0 movsd xmm0,mmword ptr [ecx+20h] addsd xmm0,mmword ptr [eax+30h] movsd mmword ptr [eax+30h],xmm0 movsd xmm0,mmword ptr [ecx+28h] addsd xmm0,mmword ptr [eax+38h] movsd mmword ptr [eax+38h],xmm0 movsd xmm0,mmword ptr [ecx+30h] addsd xmm0,mmword ptr [eax+40h] movsd mmword ptr [eax+40h],xmm0 

संपादित करें: प्रश्न प्रासंगिकता के रूप में निकला, जैसा कि व्यवहार गंभीर रूप से एरे (एन) और सीपीयू कैश के आकार पर निर्भर करता है। इसलिए यदि कोई और रुचि है, तो मैं इस प्रश्न को पुन:

क्या आप विवरणों में कुछ ठोस अंतर्दृष्टि प्रदान कर सकते हैं जो निम्नलिखित आलेखों के पांच क्षेत्रों द्वारा सचित्र अलग-अलग कैश व्यवहारों को जन्म देते हैं?

सीपीयू / कैश आर्किटेक्चर के बीच अंतर को इंगित करने में भी दिलचस्प हो सकता है, इन CPUs के लिए एक समान ग्राफ प्रदान करके।

पीपीएस: यहां पूर्ण कोड है यह TBB Tick_Count का उपयोग उच्च रिज़ॉल्यूशन टाइमिंग के लिए करता है, जिसे TBB_TIMING मैक्रो परिभाषित न करके अक्षम किया जा सकता है:

 #include <iostream> #include <iomanip> #include <cmath> #include <string> //#define TBB_TIMING #ifdef TBB_TIMING #include <tbb/tick_count.h> using tbb::tick_count; #else #include <time.h> #endif using namespace std; //#define preallocate_memory new_cont enum { new_cont, new_sep }; double *a1, *b1, *c1, *d1; void allo(int cont, int n) { switch(cont) { case new_cont: a1 = new double[n*4]; b1 = a1 + n; c1 = b1 + n; d1 = c1 + n; break; case new_sep: a1 = new double[n]; b1 = new double[n]; c1 = new double[n]; d1 = new double[n]; break; } for (int i = 0; i < n; i++) { a1[i] = 1.0; d1[i] = 1.0; c1[i] = 1.0; b1[i] = 1.0; } } void ff(int cont) { switch(cont){ case new_sep: delete[] b1; delete[] c1; delete[] d1; case new_cont: delete[] a1; } } double plain(int n, int m, int cont, int loops) { #ifndef preallocate_memory allo(cont,n); #endif #ifdef TBB_TIMING tick_count t0 = tick_count::now(); #else clock_t start = clock(); #endif if (loops == 1) { for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++){ a1[j] += b1[j]; c1[j] += d1[j]; } } } else { for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { a1[j] += b1[j]; } for (int j = 0; j < n; j++) { c1[j] += d1[j]; } } } double ret; #ifdef TBB_TIMING tick_count t1 = tick_count::now(); ret = 2.0*double(n)*double(m)/(t1-t0).seconds(); #else clock_t end = clock(); ret = 2.0*double(n)*double(m)/(double)(end - start) *double(CLOCKS_PER_SEC); #endif #ifndef preallocate_memory ff(cont); #endif return ret; } void main() { freopen("C:\\test.csv", "w", stdout); char *s = " "; string na[2] ={"new_cont", "new_sep"}; cout << "n"; for (int j = 0; j < 2; j++) for (int i = 1; i <= 2; i++) #ifdef preallocate_memory cout << s << i << "_loops_" << na[preallocate_memory]; #else cout << s << i << "_loops_" << na[j]; #endif cout << endl; long long nmax = 1000000; #ifdef preallocate_memory allo(preallocate_memory, nmax); #endif for (long long n = 1L; n < nmax; n = max(n+1, long long(n*1.2))) { const long long m = 10000000/n; cout << n; for (int j = 0; j < 2; j++) for (int i = 1; i <= 2; i++) cout << s << plain(n, m, j, i); cout << endl; } } 

(यह n विभिन्न मानकों के लिए एफएलओपी / एस दिखाता है।)

यहां छवि विवरण दर्ज करें

वेब के समाधान से एकत्रित समाधान "एक संयुक्त पाश की तुलना में अलग loops में elementwise जोड़ों में बहुत तेजी क्यों हैं?"

इसके बारे में और विश्लेषण के बाद, मेरा मानना ​​है कि यह चार बिंदुओं के डेटा संरेखण के कारण होता है (कम से कम आंशिक रूप से) यह कैश बैंक / मार्ग विरोध के कुछ स्तर का कारण होगा।

यदि मैंने सही तरीके से अनुमान लगाया है कि आप अपने सरणियों को कैसे आवंटित कर रहे हैं, तो वे पृष्ठ पंक्ति में संरेखित होने की संभावना है

इसका अर्थ है कि प्रत्येक लूप में आपके सभी अभिगम समान कैश रास्ते पर गिरेंगे। हालांकि, इंटेल प्रोसेसर को थोड़ी देर के लिए 8-रास्ता एल 1 कैशे सहयोगीयता मिली है। लेकिन वास्तविकता में, प्रदर्शन पूरी तरह एकसमान नहीं है। 4-तरीकों तक पहुंचने से 2-तरीकों से कहना धीमा हो जाता है

संपादित करें: वास्तव में ऐसा लगता है कि आप सभी सरणियों को अलग से आवंटित कर रहे हैं आम तौर पर जब इस तरह के बड़े आवंटन का अनुरोध किया जाता है, तो आबंटक ओएस से ताजा पृष्ठों का अनुरोध करेगा। इसलिए, एक उच्च मौका है कि बड़े आवंटन पृष्ठ-सीमा से ऑफ़सेट के समान दिखाई देंगे।

यहां परीक्षण कोड है:

 int main(){ const int n = 100000; #ifdef ALLOCATE_SEPERATE double *a1 = (double*)malloc(n * sizeof(double)); double *b1 = (double*)malloc(n * sizeof(double)); double *c1 = (double*)malloc(n * sizeof(double)); double *d1 = (double*)malloc(n * sizeof(double)); #else double *a1 = (double*)malloc(n * sizeof(double) * 4); double *b1 = a1 + n; double *c1 = b1 + n; double *d1 = c1 + n; #endif // Zero the data to prevent any chance of denormals. memset(a1,0,n * sizeof(double)); memset(b1,0,n * sizeof(double)); memset(c1,0,n * sizeof(double)); memset(d1,0,n * sizeof(double)); // Print the addresses cout << a1 << endl; cout << b1 << endl; cout << c1 << endl; cout << d1 << endl; clock_t start = clock(); int c = 0; while (c++ < 10000){ #if ONE_LOOP for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } #else for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } #endif } clock_t end = clock(); cout << "seconds = " << (double)(end - start) / CLOCKS_PER_SEC << endl; system("pause"); return 0; } 

बेंचमार्क परिणाम:

संपादित करें: वास्तविक कोर 2 वास्तुकला मशीन पर परिणाम:

2 x इंटेल Xeon X5482 हार्परटाउन @ 3.2 गीगाहर्ट्ज़:

 #define ALLOCATE_SEPERATE #define ONE_LOOP 00600020 006D0020 007A0020 00870020 seconds = 6.206 #define ALLOCATE_SEPERATE //#define ONE_LOOP 005E0020 006B0020 00780020 00850020 seconds = 2.116 //#define ALLOCATE_SEPERATE #define ONE_LOOP 00570020 00633520 006F6A20 007B9F20 seconds = 1.894 //#define ALLOCATE_SEPERATE //#define ONE_LOOP 008C0020 00983520 00A46A20 00B09F20 seconds = 1.993 

टिप्पणियों:

  • एक लूप के साथ 6.206 सेकंड और दो छोरों के साथ 2.116 सेकंड । यह ओपी के परिणामों को ठीक से दोहराता है

  • पहले दो परीक्षणों में, arrays को अलग से आवंटित किया जाता है आप देखेंगे कि उनके पास पेज के सापेक्ष समान संरेखण है।

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

@ स्टीफन कैनन टिप्पणी में बताते हैं, बहुत संभावना है कि यह संरेखण लोड / स्टोर इकाइयों या कैश में झूठी अलियासिंग का कारण बनता है। मैं इसके लिए चारों ओर गया और पाया कि इंटेल में आंशिक पता अलियासिंग स्टालों के लिए वास्तव में एक हार्डवेयर काउंटर है:

http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/~amplifierxe/pmw_dp/events/partial_address_alias.html


5 क्षेत्र – स्पष्टीकरण

क्षेत्र 1:

यह एक आसान है डेटासेट इतनी छोटी है कि प्रदर्शन ऊपरी हिस्से पर लूपिंग और शाखाओं की तरह होता है।

क्षेत्र 2:

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

मुझे यकीन नहीं है कि यहां क्या हो रहा है … संरेखण अभी भी एक प्रभाव डाल सकता है क्योंकि एगर फॉग कैश बैंक संघर्ष का उल्लेख करता है । (यह लिंक सैंडी पुल के बारे में है, लेकिन यह विचार अब भी कोर 2 पर लागू होगा।)

क्षेत्र 3:

इस बिंदु पर, डेटा अब एल 1 कैश में फिट नहीं है। इसलिए प्रदर्शन एल 1 <-> एल 2 कैश बैंडविड्थ द्वारा कैप्चर किया गया है।

क्षेत्र 4:

सिंगल-लूप में प्रदर्शन ड्रॉप है जिसे हम देख रहे हैं। और जैसा कि बताया गया है, यह संरेखण के कारण होता है (जो सबसे अधिक संभावना है) प्रोसेसर लोड / स्टोर इकाइयों में झूठी एलियासिंग स्टालों का कारण बनता है।

हालांकि, गलत अलियासिंग होने के क्रम में, डेटासेट्स के बीच एक बड़ी पर्याप्त प्रगति होना चाहिए। यही कारण है कि आपको यह क्षेत्र 3 में नहीं दिख रहा है।

क्षेत्र 5:

इस बिंदु पर, कुछ भी कैश में फिट नहीं है तो आप स्मृति बैंडविड्थ से बंधे हैं


2 x इंटेल X5482 हार्परटाउन @ 3.2 गीगाहर्ट्ज़इंटेल कोर i7 870 @ 2.8 गीगाहर्ट्ज़इंटेल कोर i7 2600K @ 4.4 गीगाहर्ट्ज

ठीक है, सही उत्तर में निश्चित रूप से CPU कैश के साथ कुछ करना होगा। लेकिन कैश तर्क का उपयोग करना बहुत मुश्किल हो सकता है, खासकर डेटा के बिना

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

@ मिस्टिकिक के जवाब ने बहुत से लोग (मेरे सहित) को आश्वस्त किया है, संभवतः क्योंकि यह केवल एक ही है जो तथ्यों पर भरोसा करता था, लेकिन यह सत्य का केवल एक "डेटा बिंदु" था।

यही कारण है कि मैंने अपनी परीक्षा (निरंतर बनाम अलग आवंटन का उपयोग करके) और @ जेम्स 'उत्तर की सलाह को जोड़ दिया।

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

ध्यान दें कि मेरा प्रारंभिक प्रश्न n = 100.000 था । यह बिंदु (दुर्घटना से) विशेष व्यवहार दर्शाता है:

  1. इसमें एक या दो लूप के संस्करण के बीच सबसे बड़ा विसंगति है (लगभग तीन का एक कारक)

  2. यह एकमात्र बिंदु है, जहां एक-पाश (अर्थात् निरंतर आवंटन के साथ) दो-पाश संस्करण को धड़कता है। (यह मिस्टिकिक का उत्तर संभव था, बिल्कुल।)

प्रारंभिक डेटा का उपयोग करते हुए परिणाम:

यहां छवि विवरण दर्ज करें

अप्रयुक्त डेटा का उपयोग करते हुए परिणाम (यह मिस्टिकिक परीक्षण किया गया है):

यहां छवि विवरण दर्ज करें

और यह एक कठिन-से-समझाएं है: आरंभिक डेटा, जिसे एक बार आवंटित किया जाता है और विभिन्न निम्न वेक्टर आकार के प्रत्येक परीक्षण के मामले में पुन: उपयोग किया जाता है:

यहां छवि विवरण दर्ज करें

प्रस्ताव

स्टैक ओवरफ्लो पर प्रत्येक निम्न-स्तरीय कार्यप्रदर्शन संबंधित प्रश्न की आवश्यकता पूरी तरह से कैश के प्रासंगिक डेटा आकारों के लिए MFLOPS जानकारी प्रदान करने की आवश्यकता होनी चाहिए! यह जानकारी के बारे में सोचने के लिए सभी के समय की बर्बादी है और विशेष रूप से इस जानकारी के बिना दूसरों के साथ उन पर चर्चा करें।

दूसरी पाश में बहुत कम कैश गतिविधि होती है, इसलिए प्रोसेसर के लिए स्मृति की मांगों को जारी रखना आसान होता है

कल्पना कीजिए कि आप एक मशीन पर काम कर रहे हैं जहां n केवल सही मान है, केवल एक ही समय में स्मृति में अपने दो एरे को पकड़ना संभव है, लेकिन डिस्क कैशिंग के माध्यम से उपलब्ध कुल स्मृति अभी भी चारों को पकड़ने के लिए पर्याप्त है

एक साधारण लाइफ कैशिंग नीति मानते हुए, यह कोड:

 for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } 

सबसे पहले a1 b1 और b1 को रैम में लोड किया जाएगा और फिर पूरी तरह से RAM पर काम किया जाएगा। जब दूसरी लूप शुरू होता है, तो c1 d1 और d1 तब डिस्क से रैम में लोड हो जाएंगे और इसे संचालित किया जाएगा।

अन्य लूप

 for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } 

लूप के चारों ओर हर बार हर एरेज़ और पेज को पेज पर प्रदर्शित करेगा। यह स्पष्ट रूप से बहुत धीमी हो जाएगी

संभवत: आप अपने परीक्षणों में डिस्क कैशिंग नहीं देख रहे हैं लेकिन संभवतः आपको कैशिंग के किसी अन्य रूप के दुष्परिणाम देख रहे हैं।


यहां एक छोटी सी भ्रम / गलतफहमी लगती है इसलिए मैं एक उदाहरण का उपयोग करके थोड़ा विस्तार करने का प्रयास करूंगा।

Say n = 2 और हम बाइट्स के साथ काम कर रहे हैं। मेरे परिदृश्य में हमारे पास सिर्फ 4 बाइट कैश है और बाकी की हमारी मेमोरी काफी धीमी है (100 गुना अधिक तक पहुंच)।

मान लें कि यदि बाइट कैश में नहीं है, तो इसे वहां रखें और निम्नलिखित बाइट प्राप्त करें, जब हम उस पर हैं, तो एक ऐसी मूक कैशिंग नीति मान लीजिए कि आपको इस तरह से कुछ ऐसा परिदृश्य मिलेगा:

  • साथ में

     for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } 
  • कैश a1[0] और a1[1] फिर b1[0] और b1[1] और कैश में a1[0] b1[1] a1[0] = a1[0] + b1[0] करें – कैश में अब चार बाइट हैं, a1[0], a1[1] और b1[0], b1[1] । लागत = 100 + 100

  • सेट a1[1] = a1[1] + b1[1] कैश में। लागत = 1 + 1
  • c1 और `डी 1 के लिए दोहराएं
  • कुल लागत = (100 + 100 + 1 + 1) * 2 = 404

  • साथ में

     for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } 
  • कैश a1[0] और a1[1] फिर b1[0] और b1[1] और कैश में a1[0] b1[1] a1[0] = a1[0] + b1[0] करें – कैश में अब चार बाइट हैं, a1[0], a1[1] और b1[0], b1[1] । लागत = 100 + 100

  • कैश और कैश c1[0] और c1[1] a1[0], a1[1], b1[0], b1[1] से a1[0], a1[1], b1[0], b1[1] निकाल दें, फिर d1[0] और d1[1] और सेट करें c1[0] = c1[0] + d1[0] कैश में c1[0] = c1[0] + d1[0] लागत = 100 + 100
  • मुझे संदेह है कि आप यह देखना शुरू कर रहे हैं कि मैं कहाँ जा रहा हूं
  • कुल लागत = (100 + 100 + 100 + 100) * 2 = 800

यह क्लासिक कैश थ्रैश परिदृश्य है

यह एक अलग कोड की वजह से नहीं है, लेकिन कैशिंग के कारण: रैम में सीपीयू रजिस्टरों की तुलना में धीमी है और एक कैश मेमोरी CPU के अंदर होती है ताकि हर बार जब कोई चर बदल रहा हो, लेकिन कैश बड़ा नहीं है क्योंकि रैम है, इसलिए, इसका केवल एक अंश नक्शे है

पहला कोड दूरस्थ मेमोरी पते को प्रत्येक लूप पर बारी बारी से संशोधित करता है, इस प्रकार कैश को निरस्त करने के लिए लगातार आवश्यकता होती है।

दूसरा कोड वैकल्पिक नहीं है: यह बस आसन्न पते पर दो बार प्रवाह करता है। यह कैश में पूरा होने के लिए सभी काम को पूरा करता है, इसे दूसरी लूप शुरू होने के बाद ही अमान्य करता है।

मैं यहाँ पर चर्चा की गई परिणामों को दोहरा नहीं सकता।

मैं नहीं जानता कि यदि खराब बेंचमार्क कोड को दोष देना है, या क्या है, लेकिन निम्न विधियों का उपयोग करके मेरी मशीन पर एक दूसरे के 10% के भीतर हैं, और एक लूप आमतौर पर सिर्फ दो से थोड़ा तेज है – जैसा कि आप चाहते हैं उम्मीद करते हैं।

ऐरे का आकार 2 ^ 16 से 2 ^ 24 से लेकर आठ छोरों का उपयोग करते हैं। मैं स्रोत सरणियों को आरम्भ करने के लिए सावधानी बरतता था, इसलिए += असाइनमेंट एफपीयू को दोहरी के रूप में परिभाषित स्मृति कचरा जोड़ने के लिए नहीं कह रहा था

मैंने विभिन्न योजनाओं के साथ-साथ निभाया, जैसे b[j] , d[j] को InitToZero[j] में छोरों के अंदर और साथ में += b[j] = 1 और += d[j] = 1 , और मुझे काफी अनुरूप परिणाम मिला।

जैसा कि आप उम्मीद कर सकते हैं, InitToZero[j] का उपयोग करके लूप के अंदर b और d को प्रारंभ करने से संयुक्त दृष्टिकोण को एक फायदा मिला है, क्योंकि वे a और c के काम से पहले वापस आ चुके थे, लेकिन फिर भी 10% के भीतर। जाओ पता लगाओ।

हार्डवेयर डेल एक्सपीएस 8500 पीढ़ी के 3 कोर i7 3.4 GHz और 8 जीबी मेमोरी के साथ है 2 ^ 16 से 2 ^ 24 के लिए, आठ लूपों का उपयोग करते हुए, संचयी समय क्रमशः 44.9 87 और 40.965 था। विजुअल सी ++ 2010, पूरी तरह से अनुकूलित

PS: मैंने लूप को शून्य से नीचे गिनने के लिए बदल दिया, और संयुक्त विधि मामूली तेज़ थी। मेरे सिर को खिसकाना नया सरणी आकार और पाश संख्या नोट करें

 // MemBufferMystery.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> #include <cmath> #include <string> #include <time.h> #define dbl double #define MAX_ARRAY_SZ 262145 //16777216 // AKA (2^24) #define STEP_SZ 1024 // 65536 // AKA (2^16) int _tmain(int argc, _TCHAR* argv[]) { long i, j, ArraySz = 0, LoopKnt = 1024; time_t start, Cumulative_Combined = 0, Cumulative_Separate = 0; dbl *a = NULL, *b = NULL, *c = NULL, *d = NULL, *InitToOnes = NULL; a = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); b = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); c = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); d = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); InitToOnes = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); // Initialize array to 1.0 second. for(j = 0; j< MAX_ARRAY_SZ; j++) { InitToOnes[j] = 1.0; } // Increase size of arrays and time for(ArraySz = STEP_SZ; ArraySz<MAX_ARRAY_SZ; ArraySz += STEP_SZ) { a = (dbl *)realloc(a, ArraySz * sizeof(dbl)); b = (dbl *)realloc(b, ArraySz * sizeof(dbl)); c = (dbl *)realloc(c, ArraySz * sizeof(dbl)); d = (dbl *)realloc(d, ArraySz * sizeof(dbl)); // Outside the timing loop, initialize // b and d arrays to 1.0 sec for consistent += performance. memcpy((void *)b, (void *)InitToOnes, ArraySz * sizeof(dbl)); memcpy((void *)d, (void *)InitToOnes, ArraySz * sizeof(dbl)); start = clock(); for(i = LoopKnt; i; i--) { for(j = ArraySz; j; j--) { a[j] += b[j]; c[j] += d[j]; } } Cumulative_Combined += (clock()-start); printf("\n %6i miliseconds for combined array sizes %i and %i loops", (int)(clock()-start), ArraySz, LoopKnt); start = clock(); for(i = LoopKnt; i; i--) { for(j = ArraySz; j; j--) { a[j] += b[j]; } for(j = ArraySz; j; j--) { c[j] += d[j]; } } Cumulative_Separate += (clock()-start); printf("\n %6i miliseconds for separate array sizes %i and %i loops \n", (int)(clock()-start), ArraySz, LoopKnt); } printf("\n Cumulative combined array processing took %10.3f seconds", (dbl)(Cumulative_Combined/(dbl)CLOCKS_PER_SEC)); printf("\n Cumulative seperate array processing took %10.3f seconds", (dbl)(Cumulative_Separate/(dbl)CLOCKS_PER_SEC)); getchar(); free(a); free(b); free(c); free(d); free(InitToOnes); return 0; } 

मुझे यकीन नहीं है कि यह निर्णय क्यों लिया गया कि MFLOPS एक प्रासंगिक मीट्रिक था हालांकि यह विचार स्मृति अभिगम पर ध्यान केंद्रित करना था, इसलिए मैंने फ्लोटिंग प्वाइंट कंप्यूटेशन समय की मात्रा को कम करने की कोशिश की मैं += में छोड़ दिया, लेकिन मुझे यकीन नहीं है कि क्यों

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

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

पहला पाश प्रत्येक चर में लिखता है। दूसरे और तीसरे वाले केवल तत्व आकार के छोटे से कूदते हैं

20 सेमी के दो समानांतर रेखाएं लिखकर 20 से.मी. से अलग पेन और पेपर के साथ लिखें एक बार खत्म करने और फिर दूसरी पंक्ति की कोशिश करें और एक पंक्ति में क्रॉस को एकांतर से दोबारा writting द्वारा एक बार कोशिश करो

मूल प्रश्न

दो लूपों की तुलना में एक लूप इतना धीमा क्यों है?


समस्या का मूल्यांकन

ओ पी कोड:

 const int n=100000; for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } 

तथा

 for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } 

विचार

ओपी के मूल लूप के दो प्रकारों और उनके संशोधित प्रश्न के बारे में ओपी के मूल प्रश्न को ध्यान में रखते हुए अन्य उत्कृष्ट उत्तर और उपयोगी टिप्पणियों के साथ साथ कैश के व्यवहार के प्रति; मैं इस स्थिति और समस्या के बारे में एक अलग दृष्टिकोण लेकर यहाँ कुछ अलग करने की कोशिश कर रहा हूं।


पहुंच

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


परिदृश्य

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


हम क्या जानते हैं

हम जानते हैं कि उसका लूप 100,000 बार चला जाएगा हम यह भी जानते हैं कि a1 b1 , b1 , c1 d1 और d1 एक 64-बिट वास्तुकला पर पॉइंटर्स हैं। 32-बिट मशीन पर सी ++ के भीतर सभी पॉइंटर्स 4 बाइट्स हैं और 64-बिट मशीन पर वे 8 बाइट्स आकार में हैं क्योंकि पॉइंटर्स एक निश्चित लम्बाई के हैं। हम जानते हैं कि हमारे पास 32 बाइट्स हैं, जिसमें दोनों मामलों में आवंटित करना है। एकमात्र अंतर यह है कि हम प्रत्येक चलना पर 32 बाइट्स या 2 से 2 बिट्स के 2 सेट आवंटित कर रहे हैं, जहां 2 के मामले में हम दो अलग-अलग छोरों के लिए प्रत्येक आवृत्ति के लिए 16 बाइट आवंटित कर रहे हैं। इसलिए दोनों आकृतियों की कुल आवंटियों में अभी भी 32 बाइट्स के बराबर है। इस जानकारी के साथ हम आगे बढ़ते हैं और सामान्य गणित, एल्गोरिथ्म और इसका सादृश्य दिखाते हैं। हम उस समय की राशि को जानते हैं, जो दोनों ही मामलों में एक ही सेट या ऑपरेशन का समूह होगा। हम स्मृति की मात्रा को जानते हैं जिसे दोनों मामलों में आवंटित किया जाना चाहिए। हम यह गधे कर सकते हैं कि दोनों मामलों के बीच आवंटन के कुल काम का बोझ लगभग एक ही हो जाएगा।


हम क्या नहीं जानते

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


चलिए जांच करें

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


हमारे निस्संदेह:

  • हम अपनी लूप और इसकी पुनरावृत्तियों को एक समेशन बनाते हैं जो 1 से शुरू होता है और 0 से शुरू होता है, जैसा कि लूप्स में 0 से शुरू होता है, हमें याद करने की 0 अनुक्रमित योजना के बारे में चिंता करने की ज़रूरत नहीं है, क्योंकि हम इसमें रुचि रखते हैं। एल्गोरिदम खुद
  • दोनों मामलों में हमारे पास प्रत्येक फ़ंक्शन कॉल पर 2 ऑपरेशंस के साथ काम करने के लिए 4 फ़ंक्शन हैं और 2 फ़ंक्शन कॉल्स हैं। इसलिए हम इन्हें फ़ंक्शंस और फ़ंक्शन कॉल्स के रूप में सेट करेंगे जैसे F1() , F2() , f(a) , f(b) , f(c) और f(d)

एल्गोरिदम:

पहला मामला: – केवल एक योग, लेकिन दो स्वतंत्र फ़ंक्शन कॉल।

 Sum n=1 : [1,100000] = F1(), F2(); F1() = { f(a) = f(a) + f(b); } F2() = { f(c) = f(c) + f(d); } 

दूसरा मामला: – दो सारांश लेकिन प्रत्येक के पास अपना फ़ंक्शन कॉल है।

 Sum1 n=1 : [1,100000] = F1(); F1() = { f(a) = f(a) + f(b); } Sum2 n=1 : [1,100000] = F1(); F1() = { f(c) = f(c) + f(d); } 

यदि आपने देखा है कि F2() केवल Sum में मौजूद है, जहां Sum1 और Sum2 दोनों में केवल F1() शामिल है यह भी बाद में भी स्पष्ट होगा जब हम यह निष्कर्ष निकालना शुरू कर देंगे कि दूसरे एल्गोरिथ्म से एक अनुकूलन हो रहा है।

पहले मामले के माध्यम से पुनरावृत्त Sum कॉल f(a) जो अपने स्वयं f(b) जोड़ देगा, तो यह f(c) कॉल करता है जो कि ऐसा करेगा, लेकिन प्रत्येक 100000 iterations लिए खुद को f(d) को जोड़ देगा। In the second case we have Sum1 and Sum2 And both act the same as if they were the same function being called twice in a row. In this case we can treat Sum1 and Sum2 as just plain old Sum where Sum in this case looks like this: Sum n=1 : [1,100000] { f(a) = f(a) + f(b); } and now this looks like an optimization where we can just consider it to be the same function.


Summary with Analogy

With what we seen in the second case it almost appears as if there is optimization since both for loops have the same exact signature, but this isn't the real issue. The issue isn't the work that is being done by f(a) , f(b) , f(c) & f(d) in both cases and the comparison between the two it is the difference in the distance that the Summation has to travel in both cases that gives you the difference in time execution.

Think of the For Loops as being the Summations that does the iterations as being a Boss that is giving orders to two people A & B and that their jobs are to meat C & D respectively and to pick up some package from them and return it. In the analogy here the for loop or summation iterations and condition checks themselves doesn't actually represent the Boss . What actually represents the Boss here is not from the actual mathematical algorithms directly, but from the actual concept of Scope and Code Block within a routine or sub-routine, method, function, translation unit, etc. The first algorithm has 1 scope where the 2nd algorithm has 2 consecutive scopes.

Within the first case on each call slip the Boss goes to A and gives the order and A goes off to fetch B's package then the Boss goes to C and gives the orders to do the same and receive the package from D on each iteration.

Within the second case the Boss works directly with A to go and fetch B's package until all packages are received. Then the Boss works with C to do the same for getting all of D's packages.

Since we are working with an 8 byte pointer and dealing with Heap allocation let's consider this problem here. Let us say that the Boss is 100 feet from A and that A is 500 feet from C . We don't need to worry about how far the Boss is initially from C because of the order of executions. In both cases the Boss initially travels from A first then to B . This analogy isn't to say that this distance is exact; it is just a use test case scenario to show the workings of the algorithms. In many cases when doing heap allocations and working with the cache and page files, these distances between address locations may not vary that much in differences or they can very significantly depending on the nature of the data types and the array sizes.


The Test Cases:

First Case: On first iteration the Boss has to initially go 100 feet to give the order slip to A and A goes off and does his thing, but then the Boss has to travel 500 feet to C to give him his order slip. Then on the next iteration and every other iteration after the Boss has to go back and forth 500 feet between the two.

Second Case: The Boss has to travel 100 feet on the first iteration to A , but after that he is already there and just waits for A to get back until all slips are filled. Then the Boss has to travel 500 feet on the first iteration to C because C is 500 feet from A since this Boss( Summation, For Loop ) is being called right after working with A and then just waits like he did with A until all of C's order slips are done.


The Difference In Distances Traveled

 const n = 100000 distTraveledOfFirst = (100 + 500) + ((n-1)*(500 + 500); // Simplify distTraveledOfFirst = 600 + (99999*100); distTraveledOfFirst = 600 + 9999900; distTraveledOfFirst = 10000500; // Distance Traveled On First Algorithm = 10,000,500ft distTraveledOfSecond = 100 + 500 = 600; // Distance Traveled On Second Algorithm = 600ft; 

The Comparison of Arbitrary Values

We can easily see that 600 is far less than 10 million. Now this isn't exact, because we don't know the actual difference in distance between which address of RAM or from which Cache or Page File each call on each iteration is going to be due to many other unseen variables, but this is just an assessment of the situation to be aware of and trying to look at it from the worst case scenario.

So by these numbers it would almost look as if Algorithm One should be 99% slower than Algorithm Two; however, this is only the The Boss's part or responsibility of the algorithms and it doesn't account for the actual workers A , B , C , & D and what they have to do on each and every iteration of the Loop. So the bosses job only accounts for about 15 – 40% of the total work being done. So the bulk of the work which is done through the workers has a slight bigger impact towards keeping the ratio of the speed rate differences to about 50-70%


The Observation:The differences between the two algorithms

In this situation it is the structure of the process of the work being done and it does go to show that Case 2 is more efficient from both that partial optimization of having a similar function declaration and definition where it is only the variables that differ by name. And we also see that the total distance traveled in Case 1 is much farther than it is in Case 2 and we can consider this distance traveled our Time Factor between the two algorithms. Case 1 has considerable more work to do than Case 2 does. This was also seen in the evidence of the ASM that was shown between both cases. Even with what was already said about these cases, it also doesn't account for the fact that in Case 1 the boss will have to wait for both A & C to get back before he can go back to A again on the next iteration and it also doesn't account for the fact that if A or B is taking an extremely long time then both the Boss and the other worker(s) are also waiting at an idle. In Case 2 the only one being idle is the Boss until the worker gets back. So even this has an impact on the algorithm.


निष्कर्ष:

Case 1 is a classic interpolation problem that happens to be an inefficient one. I also think that this was one of the leading reasons of why many machine architectures and developers ended up building and designing multi-core systems with the ability to do multi-threaded applications as well as parallel programming.

So even looking at it from this approach without even involving how the Hardware, OS and Compiler works together to do heap allocations that involves working with RAM, Cache, Page Files, etc.; the mathematics behind it already shows us which of these two is the better solution from using the above analogy where the Boss or the Summations being those the For Loops that had to travel between Workers A & B . We can easily see that Case 2 is at least as 1/2 as fast if not a little more than Case 1 due to the difference in the distanced traveled and the time taken. And this math lines up almost virtually and perfectly with both the Bench Mark Times as well as the Amount of difference in the amount of Assembly Instructions.



The OPs Amended Question(s)

EDIT: The question turned out to be of no relevance, as the behavior severely depends on the sizes of the arrays (n) and the CPU cache. So if there is further interest, I rephrase the question:

Could you provide some solid insight into the details that lead to the different cache behaviors as illustrated by the five regions on the following graph?

It might also be interesting to point out the differences between CPU/cache architectures, by providing a similar graph for these CPUs.


Regarding These Questions

As I have demonstrated without a doubt, there is an underlying issue even before the Hardware and Software becomes involved. Now as for the management of memory and caching along with page files, etc. which all works together in an integrated set of systems between: The Architecture { Hardware, Firmware, some Embedded Drivers, Kernels and ASM Instruction Sets }, The OS { File and Memory Management systems, Drivers and the Registry }, The Compiler { Translation Units and Optimizations of the Source Code }, and even the Source Code itself with its set(s) of distinctive algorithms; we can already see that there is a bottleneck that is happening within the first algorithm before we even apply it to any machine with any arbitrary Architecture , OS , and Programmable Language compared to the second algorithm. So there already existed a problem before involving the intrinsics of a modern computer.


The Ending Results

तथापि; it is not to say that these new questions are not of importance because they themselves are and they do play a role after all. They do impact the procedures and the overall performance and that is evident with the various graphs and assessments from many who have given their answer(s) and or comment(s). If you pay attention to the analogy of the Boss and the two workers A & B who had to go and retrieve packages from C & D respectively and considering the mathematical notations of the two algorithms in question you can see that without even the involvement of the computer Case 2 is approximately 60% faster than Case 1 and when you look at the graphs and charts after these algorithms have been applied to source code, compiled and optimized and executed through the OS to perform operations on the given hardware you even see a little more degradation between the differences in these algorithms.

Now if the "Data" set is fairly small it may not seem all that bad of a difference at first but since Case 1 is about 60 - 70% slower than Case 2 we can look at the growth of this function as being in terms of the differences in time executions:

 DeltaTimeDifference approximately = Loop1(time) - Loop2(time) //where Loop1(time) = Loop2(time) + (Loop2(time)*[0.6,0.7]) // approximately // So when we substitute this back into the difference equation we end up with DeltaTimeDifference approximately = (Loop2(time) + (Loop2(time)*[0.6,0.7])) - Loop2(time) // And finally we can simplify this to DeltaTimeDifference approximately = [0.6,0.7]*(Loop2(time) 

And this approximation is the average difference between these two loops both algorithmically and machine operations involving software optimizations and machine instructions. So when the data set grows linearly, so does the difference in time between the two. Algorithm 1 has more fetches than algorithm 2 which is evident when the Boss had to travel back and forth the maximum distance between A & C for every iteration after the first iteration while Algorithm 2 the Boss had to travel to A once and then after being done with A he had to travel a maximum distance only one time when going from A to C .

So trying to have the Boss focusing on doing two similar things at once and juggling them back and forth instead of focusing on similar consecutive tasks is going to make him quite angry by the end of the day because he had to travel and work twice as much. Therefor do not lose the scope of the situation by letting your boss getting into an interpolated bottleneck because the boss's spouse and children wouldn't appreciate it.