2015-11-15 8 views
7

यहां सरल सी ++ कोड है जो कॉलम प्रमुख के साथ 2 डी सरणी पंक्ति प्रमुख की तुलना करने की तुलना करता है।कॉलम प्रमुख की तुलना में 2 डी सरणी पंक्ति तेज तेज क्यों है?

#include <iostream> 
#include <ctime> 

using namespace std; 

const int d = 10000; 

int** A = new int* [d]; 

int main(int argc, const char * argv[]) { 
    for(int i = 0; i < d; ++i) 
     A[i] = new int [d]; 

    clock_t ColMajor = clock(); 

    for(int b = 0; b < d; ++b) 
     for(int a = 0; a < d; ++a) 
      A[a][b]++; 

    double col = static_cast<double>(clock() - ColMajor)/CLOCKS_PER_SEC; 

    clock_t RowMajor = clock(); 
    for(int a = 0; a < d; ++a) 
     for(int b = 0; b < d; ++b) 
      A[a][b]++; 

    double row = static_cast<double>(clock() - RowMajor)/CLOCKS_PER_SEC; 



    cout << "Row Major : " << row; 
    cout << "\nColumn Major : " << col; 

    return 0; 
} 

के विभिन्न मूल्यों के लिए परिणाम:

घ = 10^3:

पंक्ति मेजर: 0,002431

कॉलम मेजर: 0,017186

d = 10^4:

पंक्ति मेजर: 0,237995

कॉलम मेजर: 2,04471

d = 10^5

पंक्ति मेजर: 53.9561

कॉलम मेजर: 444.339

अब सवाल यह है कि पंक्ति प्रमुख कॉलम प्रमुख से तेज क्यों है?

+4

क्योंकि सी सरणी में ** पंक्ति प्रमुख ** और ** कैश ** ** के स्थानिक इलाके ** की वजह से है। – bolov

+1

संभावित डुप्लिकेट [कैश इलाके सरणी प्रदर्शन के लिए क्यों मायने रखता है?] (Http://stackoverflow.com/questions/12065774/why-does-cache-locality-matter-for-array-performance) – bolov

+0

इस बार यह नहीं है शाखा भविष्यवाणी :)। दोनों संस्करणों में आपके पास समान संख्या की तुलना होती है, और दोनों बार 'सत्य'/'झूठा' पैटर्न समान होता है (यानी कई 'सत्य' स्थितियां और फिर 'झूठी' एक - जब सूचकांक अंत तक पहुंच जाता है) – bolov

उत्तर

10

यह स्पष्ट रूप से मशीन पर आप हैं, लेकिन बहुत आम तौर पर बोल रहा है पर निर्भर करता है: जब प्रतिकारी एक कैश मुख्य स्मृति से एक बहुत छोटे विलंबता (यहां तक ​​कि है कि में अपने कार्यक्रमों स्मृति के

  1. आपके कंप्यूटर भंडार भागों कैश हिट टाइम के लिए)।

  2. सी सरणी पंक्ति प्रमुख क्रम द्वारा एक संगत में संग्रहीत हैं। इसका अर्थ यह है कि यदि आप तत्व x के लिए पूछते हैं, तो तत्व x+1 मुख्य स्मृति में सीधे स्थान पर संग्रहीत किया जाता है जहां x संग्रहीत किया जाता है।

  3. यह आपके कंप्यूटर कैश के लिए सामान्य रूप से "पूर्व-खाली" कैश भरने वाले मेमोरी पतों के साथ सामान्य है, जो अभी तक उपयोग नहीं किए गए हैं, लेकिन यह स्थानीय रूप से स्मृति के करीब हैं जो आपके प्रोग्राम ने पहले से ही उपयोग किया है। अपने कंप्यूटर के बारे में सोचें: "ठीक है, आप पता एक्स पर स्मृति चाहते थे इसलिए मैं यह मानने जा रहा हूं कि आप जल्द ही एक्स + 1 पर मेमोरी चाहते हैं, इसलिए मैं इसे आपके लिए पहले से ही पकड़ लेगा और इसे अपने कैश में रखूंगा" ।

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

भी, यह https://cs.stackexchange.com/ के लिए बेहतर अनुकूल हो सकता है क्योंकि आपके सिस्टम कैश व्यवहार के तरीके हार्डवेयर में लागू किया गया है, और स्थानिक इलाके के प्रश्न बेहतर के लिए उपयुक्त लगते हैं।

+0

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

+0

थोड़ा नाइटपिकिंग, लेकिन बिंदु (2) के बारे में, स्तंभ-प्रमुख और पंक्ति-प्रमुख एक आयाम के समान हैं। अंतिम सूचकांक पंक्ति-प्रमुख में सबसे तेजी से बढ़ता है, जबकि पहली अनुक्रमणिका कॉलम-प्रमुख में सबसे तेज़ी से बढ़ जाती है, जो एक आयाम के समान होती है। दो आयाम, 'x [0] [0..10] 'पंक्ति-प्रमुख के साथ स्मृति में संयोग से रखे जाएंगे, जबकि' x [0..10] [0]' कॉलम प्रमुख के साथ संगत रूप से रखा जाएगा। – Jason

5

आपकी सरणी वास्तव में ragged array है, इसलिए पंक्ति प्रमुख पूरी तरह से एक कारक नहीं है।

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

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

आप एक सच्चे दो आयामी सरणी चाहते हैं, रो-प्रमुख आदेश का उपयोग कर स्मृति में तैयार कर ली है जो आप चाहेंगे ...

int A[1000][1000]; 

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

2

संक्षिप्त उत्तर सीपीयू कैश है। स्कॉट मेयर इसे स्पष्ट रूप से स्पष्ट करता है here

संबंधित मुद्दे