2012-07-01 19 views
18

आज मैंने std::vector और std::array की जीसीसी अनुकूलन में कुछ मतभेदों की तुलना करने और तुलना करने का निर्णय लिया। आम तौर पर, मैंने पाया कि मुझे क्या उम्मीद है: छोटे सरणी के संग्रह पर प्रत्येक कार्य करने के लिए एक संग्रह समकक्ष वैक्टर पर कार्यों को करने से कहीं अधिक तेज है।क्यों <T, N> वेक्टर <T> से धीमा हो जाएगा?

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

किसी भी विचार क्यों std::vector होगा कभी मात std::array (जिस पर संकलक अधिक संकलन समय की जानकारी है)?

मैंने gcc-4.7 -std=c++11 -O3 (gcc-4.6 -std=c++0x -O3 का उपयोग करके संकलित इस परिणाम में भी होना चाहिए)। रनटाइम की गणना bash -native time कमांड (उपयोगकर्ता समय) का उपयोग करके की गई थी।

कोड:

#include <array> 
#include <vector> 
#include <iostream> 
#include <assert.h> 
#include <algorithm> 

template <typename VEC> 
double fast_sq_dist(const VEC & lhs, const VEC & rhs) { 
    assert(lhs.size() == rhs.size()); 
    double result = 0.0; 
    for (int k=0; k<lhs.size(); ++k) { 
    double tmp = lhs[k] - rhs[k]; 
    result += tmp * tmp; 
    } 
    return result; 
} 

int main() { 
    const std::size_t K = 20000; 
    const std::size_t N = 4; 

    // declare the data structure for the collection 
    // (uncomment exactly one of these to time it) 

    // array of arrays 
    // runtime: 1.32s 
    std::array<std::array<double, N>, K > mat; 

    // array of arrays (allocated on the heap) 
    // runtime: 1.33s 
    // std::array<std::array<double, N>, K > & mat = *new std::array<std::array<double, N>, K >; 

    // C-style heap array of arrays 
    // runtime: 0.93s 
    // std::array<double, N> * mat = new std::array<double, N>[K]; 

    // vector of arrays 
    // runtime: 0.93 
    // std::vector<std::array<double, N> > mat(K); 

    // vector of vectors 
    // runtime: 2.16s 
    // std::vector<std::vector<double> > mat(K, std::vector<double>(N)); 

    // fill the collection with some arbitrary values 
    for (std::size_t k=0; k<K; ++k) { 
    for (std::size_t j=0; j<N; ++j) 
     mat[k][j] = k*N+j; 
    } 

    std::cerr << "constructed" << std::endl; 

    // compute the sum of all pairwise distances in the collection 
    double tot = 0.0; 
    for (std::size_t j=0; j<K; ++j) { 
    for (std::size_t k=0; k<K; ++k) 
     tot += fast_sq_dist(mat[j], mat[k]); 
    } 

    std::cout << tot << std::endl; 

    return 0; 
} 

नायब 1: सभी संस्करणों एक ही परिणाम मुद्रित करें।

एनबी 2: और बस प्रदर्शित करने के लिए है कि दोनों के बीच मतभेद क्रम std::array<std::array<double, N>, K>, std::vector<std::array<double, N> >, और std::vector<std::vector<double> > था नहीं बस काम/प्रारंभ से जब आवंटन, बस (यानी गणना और मुद्रण बाहर टिप्पणी संग्रह आवंटन की runtimes tot) क्रमशः 0.000s, 0.000s और 0.004s थे।

एनबी 3: कैशिंग में अनुचित मतभेदों को रोकने के लिए प्रत्येक विधि को संकलित और अलग-अलग चलाया जाता है (उसी निष्पादन योग्य के भीतर बैक-टू-बैक नहीं)।

नायब 4: सरणियों की सरणी के लिए
विधानसभा: http://ideone.com/SM8dB
सरणियों के वेक्टर के लिए विधानसभा: http://ideone.com/vhpJv
वैक्टर की वेक्टर के लिए विधानसभा: बस बिल्कुल स्पष्ट होना: http://ideone.com/RZTNE

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

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

यदि आप मेरे जैसे उत्सुक हैं, तो मुझे आपकी मदद करने में आपकी मदद पसंद आएगी।

+5

अधिक सटीक मान देखने के लिए इसे 1000 की पुनरावृत्ति गणना के साथ चलाने का प्रयास करें। वे दिखते हैं कि वे विलंबता मूल्य हो सकते हैं। –

+0

@ कोलेजोहनसन क्या आपका मतलब 'एन = 1000' या' के = 1000' है? यदि आपका मतलब 'एन = 1000' है, तो सरणी का वेक्टर लगभग वैक्टरों के वेक्टर के समान होता है (क्योंकि लूप को अनलोल करने का ओवरहेड बहुत अधिक होता है)। 'एन = 1' का उपयोग वेक्टर के वेक्टर और वेक्टर के वेक्टर के बीच बहुत अधिक अंतर में होता है, क्योंकि सरणी के वेक्टर को अनिवार्य रूप से डबल के वेक्टर में परिवर्तित किया जाना चाहिए। इसलिए सरणी और सरणी के वेक्टर की सरणी की तुलना करने का सबसे दिलचस्प मामला 'के << एन' (' << 'गणित के अर्थ में, बिट शिफ्ट भावना नहीं है)। – user

+0

यदि आप दो परीक्षणों को स्वैप करते हैं तो क्या होता है? – Mehrdad

उत्तर

0

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

+2

हू, क्या यह वास्तव में कुछ ओएस कर सकता है? मुझे लगता है कि स्टैक को पुन: आवंटित करने से कार्यक्रम के किसी भी पॉइंटर्स-टू-स्टैक-ऑब्जेक्ट्स को अमान्य कर दिया जाएगा, जिसके परिणामस्वरूप प्रोग्राम की संभावना दुर्घटना हो सकती है ... –

+1

यह सुनिश्चित करने के लिए कि यह इस तरह के ढेर का उपयोग करके नहीं हुआ है, मेरे ऊपर एक परीक्षण है जहां मैं ढेर पर सरणी की सरणी आवंटित करता हूं - मुझे एक ही रनटाइम मिलता है। – user

+0

@ जेरेमी: हाँ यह है। पुनर्वितरण कोई समस्या नहीं है क्योंकि ढेर का पता ढेर से वर्चुअल मेमोरी एड्रेस स्पेस के दूसरे सिरे और एमएमएपी के साथ आवंटित चीजों से नीचे है। भौतिक पृष्ठों को अंत में मैप किया जा सकता है। – notlostyet

3

के अंत में डंपिंग/proc/self/maps का प्रयास करें दूसरे और तीसरे परीक्षणों पर विचार करें। संकल्पनात्मक रूप से, वे समान हैं: ढेर से K * N * sizeof(double) बाइट आवंटित करें और फिर उन्हें उसी तरह से एक्सेस करें। तो अलग-अलग समय क्यों?

आपके सभी "तेज़" परीक्षणों में एक बात आम है: new[]। सभी धीमे परीक्षण new या स्टैक पर आवंटित किए जाते हैं। vector शायद हूड ™ के तहत new[] का उपयोग करता है। इसके लिए एकमात्र स्पष्ट कारण यह है कि new[] और new में अपेक्षित अपेक्षाकृत अधिक महत्वपूर्ण कार्यान्वयन हैं।

जो मैं सुझाव देने जा रहा हूं वह यह है कि new[]mmap पर वापस आ जाएगा और सीधे एक पृष्ठ सीमा पर आवंटित होगा, जिससे आपको संरेखण गति मिल जाएगी, जबकि अन्य दो विधियां पृष्ठ सीमा पर आवंटित नहीं होंगी।

प्रतिबद्ध पृष्ठों को सीधे मैप करने के लिए ओएस आवंटन फ़ंक्शन का उपयोग करने पर विचार करें, और उसके बाद std::array<std::array<double, N>, K> रखें।

+0

मैंने 'std :: array , के> और mat = * new std :: array , के> [1];' 'नया []' का उपयोग करने के लिए प्रयास किया, लेकिन यह उसी रनटाइम को सरणी के सरणी के रूप में देता है ... – user

+10

जब तक आप ऐसा करने के लिए आवंटन की आपूर्ति नहीं करते हैं, 'वेक्टर' हुड के तहत 'नया [] '" उपयोग नहीं करेगा। यह जो भी आवंटक आपूर्ति करता है उसका उपयोग करता है। जब तक आप अन्यथा निर्दिष्ट नहीं करते हैं, तो यह 'std :: आवंटक ' का उपयोग करता है। बदले में, कच्चे मेमोरी को आवंटित करने के लिए 'ऑपरेटर न्यू' का उपयोग किया जाएगा। –

+0

ओह हाँ। आवंटकों के बारे में भूल गए। – Puppy

1

सरल स्पष्टीकरण की खोज न करें जब सरल लोग पर्याप्त हों। यह एक अनुकूलक बग है। सादा पुरानी फिक्स्ड-साइज सी-स्टाइल स्टैक-आवंटित सरणी std::array के समान प्रदर्शन देती है, इसलिए std::array कार्यान्वयन को दोष न दें।

+0

मैंने यह नहीं कहा कि आपने एसटीएल को दोषी ठहराया है। मैं केवल इतना कह रहा हूं कि आपको नहीं चाहिए, बस मामले में। बीटीडब्ल्यू मैंने इसे -ओ 2 के साथ करने की कोशिश की है और सभी प्रकारों में मेरी मशीन पर वस्तुतः समान प्रदर्शन है। –

+0

दिलचस्प ... शायद अगर आपने 'के' बढ़ाने की कोशिश की है? मैं कोर i7 पर चल रहा हूं, लेकिन फिर भी एक लैपटॉप, इसलिए इसे बेहतर हार्डवेयर पर स्पष्ट होने के लिए बड़े पैमाने की आवश्यकता हो सकती है। भले ही, मुझे आश्चर्य हुआ कि सरणी का वेक्टर आपके लिए वैक्टरों के वेक्टर से तेज़ नहीं था - जो मुझे सहज ज्ञान देता है (जब 'के' 'एन' से बड़ा होता है)। क्या यह आपके लिए आश्चर्यजनक नहीं है? – user

0

मुझे लगता है कि केवल एक बड़ा अंतर यह है कि आपका डेटा अलग-अलग संग्रहीत किया जाता है। आपके पहले दो मामलों में आपका डेटा एक विशाल हिस्से में संग्रहीत होता है। अन्य सभी मामले आपके मैट्रिक्स में पंक्तियों को स्टोर पॉइंटर्स करते हैं। मुझे नहीं पता कि यह आपके कोड को तेज़ी से क्यों बनाता है लेकिन यह लुकअप और सीपीयू प्रीफेचिंग से संबंधित हो सकता है। इससे पहले कि आप इसे फिर से शुरू करने से पहले अपनी मैट्रिक्स पंक्ति को कैशिंग करें, इसलिए आपको प्रत्येक प्रविष्टि के लिए mat[k] देखने की आवश्यकता नहीं है। इससे इसे तेज और गति भी मिल सकती है। यह हो सकता है कि आपका कंपाइलर vector<array<T>> मामले में ऐसा कर सकता है लेकिन array<array<T>> मामले में नहीं।

+0

मुझे लगता है कि 'सर > 'और' वेक्टर > 'दोनों इसे एक बड़े ब्लॉक में स्टोर करते हैं (वेक्टर को ढेर पर ब्लॉक को छोड़कर)। 'सर >' या 'वेक्टर >' आप जो भी कह रहे हैं उससे अधिक करें (पॉइंटर्स का संग्रह संग्रहीत करना, प्रत्येक पंक्ति के लिए एक)। – user

+0

@ ओलिवर: आप सही हैं। – Florian

1

मैंने बस इसे अपने डेस्कटॉप पर एमएसवीसी ++ 2010 के साथ करने की कोशिश की, और मुझे vectors को छोड़कर सभी परीक्षणों के लिए एक ही समय (1.6 सेकंड) मिला जो 5.0 सेकंड था।

मैं आपके पुस्तकालयों को array और vector के वास्तविक कार्यान्वयन को देखने के लिए विचार करता हूं कि क्या कोई स्पष्ट अंतर है या नहीं।

इटेटर-स्टाइल लूप के साथ इंडेक्स-स्टाइल लूप को बदलने का प्रयास करें और देखें कि यह प्रदर्शन को प्रभावित करता है या नहीं।

इसके अलावा, प्रोग्राम के भीतर से अपने प्रोग्राम के समय clock() का उपयोग करने का प्रयास करें: अन्य चीजों के साथ, यह आपको बताएगा कि कोड का कौन सा हिस्सा अलग-अलग काम कर रहा है। यह घोंसला वाले दायरे में जोड़ने के लायक भी हो सकता है ताकि आप ऑब्जेक्ट विनाशकों को भी समय दे सकें।

3

मुझे लगता है कि बस जब vector के संभाजक यह शायद operator new उपयोग कर रहा है स्मृति उपयुक्त रूप किसी भी प्रकार के लिए गठबंधन वापस जाने के लिए है जो का उपयोग कर array थोड़ी देर के लिए संरेखित करने के लिए है जब स्टैक पर array आवंटन या संकलक का ढेर।अगर आवंटित स्मृति को बेहतर ढंग से गठबंधन किया जाता है तो अधिक कैश हिट/बड़ा पढ़ता है, तो ऐसा लगता है कि यह प्रदर्शन अंतर को आसानी से समझा सकता है।

+0

+1 अच्छा विचार। मैंने पहले से ही 'int' के साथ आंतरिक प्रकार (समान परिणामों के साथ) के साथ कोशिश की है, लेकिन मुझे आश्चर्य है कि अन्य प्रकारों का उपयोग करके सरणी को बेहतर तरीके से संरेखित किया जाएगा? हो सकता है कि 'फ्लोट', 'char', 'T *', आदि के साथ प्रयास करने लायक हो। इसके अलावा, आपका उत्तर बताएगा कि' -O0', '-O', और '-O3' पर ऑप्टिमाइज़ेशन के साथ गति अंतर अभी भी क्यों होता है। – user

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