क्या एसटीडी :: वेक्टर सादे सरणियों की तुलना में बहुत धीमा है?


212

मैंने हमेशा सोचा है कि यह सामान्य ज्ञान है std::vector "एक सरणी के रूप में लागू किया जाता है," ब्ला ब्ला ब्ला। आज मैंने नीचे जाकर उसका परीक्षण किया, और ऐसा लगता है कि ऐसा नहीं है:

यहाँ कुछ परीक्षण के परिणाम हैं:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

यह लगभग 3 - 4 बार धीमा है! वास्तव में " vectorकुछ नैनोसेकल्स के लिए धीमा हो सकता है" टिप्पणियों के लिए उचित नहीं है ।

और मेरे द्वारा उपयोग किया गया कोड:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

क्या मैं इसे गलत या कुछ कर रहा हूं? या मैंने सिर्फ इस प्रदर्शन मिथक का भंडाफोड़ किया है?

मैं Visual Studio 2005 में रिलीज़ मोड का उपयोग कर रहा हूँ ।


में विजुअल C ++ , #define _SECURE_SCL 0कम कर देता है UseVectorआधे से (यह लाने नीचे 4 सेकंड के लिए)। यह वास्तव में बहुत बड़ा है, IMO।


23
जब आप डीबग मोड में होते हैं, तो वेक्टर के कुछ संस्करण यह जाँचने के लिए अतिरिक्त निर्देश जोड़ते हैं कि आप ऐरे के अंत और सामान की तरह पहुँच से बाहर नहीं हैं। वास्तविक समय प्राप्त करने के लिए आपको रिलीज़ मोड में निर्माण करना होगा और अनुकूलन को चालू करना होगा।
मार्टिन यॉर्क

40
यह अच्छा है कि आपने इंटरनेट पर सुनाई जाने वाले दावों पर विश्वास करने के बजाय मापा है।
पी शेव्ड

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

37
योग्य, "सिंक में गंदे बर्तन फेंकने" और "गंदे बर्तन को सिंक में फेंकने और अगर वे नहीं टूटे तो जाँचें"?
इमरे एल

9
VC2010 पर कम से कम यह लगता है कि प्रमुख अंतर यह है कि मॉलॉक () आकार में आकार परिवर्तन () से अधिक है। मेमोरी आवंटन को समय से निकालें, _ITERATOR_DEBUG_LEVEL == 0 के साथ संकलित करें और परिणाम समान हैं।
एंड्रियास मैग्यूसन

जवाबों:


260

निम्नलिखित का उपयोग करना:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray को 2.196 सेकंड में
पूरा किया गया।
UseVector को 4.412 सेकंड में पूरा किया गया UseVectorPushBack 8.017 सेकंड में पूरा हुआ
। पूरी बात 14.626 सेकंड में पूरी हुई

तो सरणी वेक्टर की तुलना में दोगुनी है।

लेकिन कोड को अधिक विस्तार से देखने के बाद यह अपेक्षित है; जैसा कि आप सदिश में दो बार और सरणी केवल एक बार चलाते हैं। नोट: जब आपresize() वेक्टर करते हैं तो आप न केवल मेमोरी आवंटित कर रहे हैं बल्कि वेक्टर के माध्यम से चल रहे हैं और प्रत्येक सदस्य पर कंस्ट्रक्टर को कॉल कर रहे हैं।

कोड को फिर से व्यवस्थित करना ताकि वेक्टर केवल प्रत्येक वस्तु को एक बार इनिशियलाइज़ करे:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

अब फिर से वही टाइमिंग कर रहे हैं:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out का
उपयोग करने वाले ने 2.216 सेकंड में पूरा किया

वेक्टर अब केवल सरणी से थोड़ा खराब प्रदर्शन करता है। IMO यह अंतर नगण्य है और परीक्षण से जुड़ी चीजों की एक पूरी गुच्छा के कारण हो सकता है।

मैं इस बात को भी ध्यान में रखूंगा कि आप पिक्सेल वस्तु को सही ढंग से आरंभीकृत / नष्ट नहीं कर रहे हैं UseArrray()क्योंकि न तो कंस्ट्रक्टर / डिस्ट्रक्टर को बुलाया जाता है (यह इस साधारण वर्ग के लिए मुद्दा नहीं हो सकता है लेकिन कुछ और अधिक जटिल हो सकता है (यानी पॉइंटर्स या सदस्यों के साथ) संकेत के साथ) समस्याओं का कारण होगा।


48
@ kizzx2: आपको reserve()इसके बजाय उपयोग करने की आवश्यकता है resize()। यह वस्तुओं के लिए स्थान आवंटित करता है (अर्थात, यह वेक्टर की क्षमता को बदलता है ) लेकिन वस्तुओं को नहीं बनाता है (अर्थात, वेक्टर का आकार अपरिवर्तित रहता है)।
जेम्स मैकनेलिस

25
आप 1 000 000 000 एरे एक्सेस कर रहे हैं। समय अंतर 0.333 सेकंड है। या सरणी के उपयोग के लिए 0.000000000333 का अंतर। मान लें कि २.३३ गीगाहर्ट्ज प्रोसेसर मेरी तरह ०. stages इंस्ट्रक्शन पाइपलाइन चरणों के अनुसार एरे एक्सेस है। तो वेक्टर ऐसा दिखता है जैसे कि वह प्रति एक्स्ट्रा इंस्ट्रक्शन्स का उपयोग कर रहा है।
मार्टिन यॉर्क

3
@ जेम्स मैकनेलिस: आप सिर्फ इसके resize()साथ प्रतिस्थापित नहीं कर सकते हैं reserve(), क्योंकि यह वेक्टर के आंतरिक विचार को अपने आकार से समायोजित नहीं करता है, इसलिए बाद में इसके तत्वों को लिखते हैं तकनीकी रूप से "अंत का अतीत" लिख रहे हैं और यूबी का उत्पादन करेंगे। हालांकि व्यवहार में प्रत्येक एसटीएल कार्यान्वयन उस संबंध में "खुद का व्यवहार करेगा", आप वेक्टर के आकार को कैसे फिर से व्यवस्थित करते हैं? यदि आप वेक्टर को पॉपुलेट करने के resize() बाद कॉल करने का प्रयास करते हैं , तो यह संभवतः उन सभी तत्वों को डिफ़ॉल्ट-निर्मित मानों के साथ अधिलेखित कर देगा!
j_random_hacker

8
@j_random_hacker: क्या मैंने ऐसा नहीं कहा है? मुझे लगा कि मैं बहुत स्पष्ट था कि reserveकेवल एक वेक्टर की क्षमता को बदलता है, न कि उसके आकार को।
जेम्स मैकनेलिस

7
ठीक है, जाओ आंकड़ा। वेक्टर विधियों में बहुत सारे अपवाद-संबंधित क्रॉफ्ट थे। /EHscसंकलन स्विच में जोड़ने से सफाई हो जाती है, और assign()वास्तव में अब सरणी होती है। वाह।
पावेल मिनेव

55

बड़ा सवाल है। मैं यहाँ आया था कुछ सरल फिक्स खोजने की उम्मीद में जो सदिश परीक्षणों को गति देगा। मैं उम्मीद की तरह काफी काम नहीं किया!

अनुकूलन मदद करता है, लेकिन यह पर्याप्त नहीं है। अनुकूलन पर मैं अभी भी UseArray और UseVector के बीच 2X प्रदर्शन अंतर देख रहा हूं। दिलचस्प है, UseVector अनुकूलन के बिना UseVectorPushBack की तुलना में काफी धीमा था।

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

आइडिया # 1 - मॉलॉक के बजाय नए [] का उपयोग करें

मैं बदल रहा है की कोशिश की malloc()करने के लिए new[]इसलिए वस्तुओं का निर्माण किया जायेगा UseArray में। और एक पिक्सेल उदाहरण निर्दिष्ट करने के लिए व्यक्तिगत क्षेत्र असाइनमेंट से बदल रहा है। ओह, और आंतरिक लूप चर का नाम बदलकर j

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

हैरानी की बात है (मेरे लिए), उन परिवर्तनों में से कोई भी कोई फर्क नहीं पड़ा। यह भी नहीं कि new[]कौन सा परिवर्तन डिफ़ॉल्ट रूप से सभी पिक्सेल का निर्माण करेगा। ऐसा लगता है कि जीसीसी का उपयोग करते समय डिफ़ॉल्ट कंस्ट्रक्टर कॉल को अनुकूलित कर सकते हैं new[], लेकिन उपयोग करते समय नहींvector

आइडिया # 2 - बार-बार ऑपरेटर [] कॉल निकालें

मैंने ट्रिपल operator[]लुकअप से छुटकारा पाने का भी प्रयास किया और संदर्भ को कैश किया pixels[j]। यह वास्तव में UseVector धीमा कर दिया! उफ़।

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

आइडिया # 3 - निर्माणकर्ताओं को हटा दें

निर्माणकर्ताओं को पूरी तरह से हटाने के बारे में क्या? तब शायद जीसीसी सभी वस्तुओं के निर्माण का अनुकूलन कर सकता है जब वैक्टर बनाए जाते हैं। यदि हम पिक्सेल को इसमें बदलते हैं तो क्या होता है:

struct Pixel
{
    unsigned char r, g, b;
};

परिणाम: लगभग 10% तेज। अभी भी एक सरणी से धीमी है। हम्म।

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

आइडिया # 4 - लूप इंडेक्स के बजाय इटरेटर का उपयोग करें

vector<Pixel>::iteratorलूप इंडेक्स के बजाय उपयोग के बारे में कैसे ?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

परिणाम:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

नहीं, अलग नहीं है। कम से कम यह धीमा नहीं है। मुझे लगा कि यह # 2 के समान प्रदर्शन होगा जहां मैंने एक Pixel&संदर्भ का उपयोग किया था ।

निष्कर्ष

यहां तक ​​कि अगर कुछ स्मार्ट कुकी आंकड़े बताते हैं कि वेक्टर लूप को सरणी एक के रूप में तेजी से कैसे बनाया जाए, तो यह डिफ़ॉल्ट व्यवहार के बारे में अच्छी तरह से बात नहीं करता है std::vector । कंपाइलर के लिए इतना स्मार्ट होना कि वह सभी C ++ नेस को ऑप्टिमाइज़ कर सके और STL कंटेनरों को कच्ची सरण के रूप में तेज़ बना सके।

लब्बोलुआब यह है कि कंपाइलर उपयोग करते समय नो-ऑप डिफ़ॉल्ट कंस्ट्रक्टर कॉल्स को ऑप्टिमाइज़ करने में असमर्थ है std::vector। यदि आप सादे का उपयोग करते हैं तो new[]यह उन्हें ठीक करने के लिए अनुकूलन करता है। लेकिन साथ नहीं std::vector। यहां तक ​​कि अगर आप अपने कोड को उस मंत्र के सामने उड़ने वाले कंस्ट्रक्टर कॉल को खत्म करने के लिए फिर से लिख सकते हैं: "संकलक आपके मुकाबले अधिक स्मार्ट है। STL सादे सी के रूप में तेज है। इसके बारे में चिंता न करें।"


2
फिर, वास्तव में कोड चलाने के लिए धन्यवाद। जब कभी कोई लोकप्रिय राय को चुनौती देने का प्रयास करता है, तो बिना कारणों के उसे पीटना आसान होता है।
kizzx2

3
"कंपाइलर के लिए इतना स्मार्ट होना कि वह सभी C ++ नेस को ऑप्टिमाइज़ कर सके और STL कंटेनर को कच्ची सरण के रूप में तेज़ बना सके।" अच्छी टिप्पणियाँ। मेरे पास एक सिद्धांत है कि यह "कंपाइलर स्मार्ट है" सिर्फ एक मिथक है - सी ++ पार्सिंग बेहद कठिन है और कंपाइलर सिर्फ एक मशीन है।
kizzx2

3
मुझे नही पता। ज़रूर, वह सरणी परीक्षण को धीमा करने में सक्षम था , लेकिन उसने वेक्टर को गति नहीं दी। मैंने ऊपर संपादित किया जहां मैंने पिक्सेल से निर्माणकर्ताओं को हटा दिया और इसे एक सरल संरचना बना दिया, और यह अभी भी धीमा था। यह सरल प्रकार का उपयोग करता है, जो किसी के लिए भी बुरी खबर है vector<int>
जॉन कुगेलमैन

2
काश मैं वास्तव में आपके उत्तर को दो बार बढ़ा पाता। स्मार्ट विचारों को आज़माने के लिए (हालांकि कोई भी वास्तव में काम नहीं किया) जो मैं सोच भी नहीं सकता था!
kizzx2

9
बस एक नोट बनाना चाहता था कि सी ++ को पार्स करने की जटिलता (जो कि बहुत जटिल है, हाँ) का अनुकूलन की गुणवत्ता से कोई लेना-देना नहीं है। उत्तरार्द्ध आमतौर पर उन चरणों में होता है जहां पार्स परिणाम पहले से कई गुना अधिक निम्न-स्तरीय प्रतिनिधित्व में बदल जाता है।
पावेल मिनेव

44

यह एक पुराना लेकिन लोकप्रिय प्रश्न है।

इस बिंदु पर, कई प्रोग्रामर C ++ 11 में काम करेंगे। और C ++ 11 में ओपी का कोड जितना लिखा जाता है उतना ही तेजी से UseArrayया के लिए चलता है UseVector

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

मूलभूत समस्या यह थी कि जब आपका Pixelढांचा असंगठित था, तो std::vector<T>::resize( size_t, T const&=T() )एक डिफ़ॉल्ट रूप से निर्मित Pixelऔर इसे कॉपी करता है । संकलक ने ध्यान नहीं दिया कि इसे असिंचित डेटा को कॉपी करने के लिए कहा जा रहा है, इसलिए इसने वास्तव में प्रतिलिपि का प्रदर्शन किया।

C ++ 11 में, std::vector<T>::resizeदो अधिभार हैं। पहला है std::vector<T>::resize(size_t), दूसरा है std::vector<T>::resize(size_t, T const&)। इसका मतलब है कि जब आप resizeएक दूसरे तर्क के बिना आह्वान करते हैं , तो यह बस डिफ़ॉल्ट निर्माण करता है, और कंपाइलर को यह महसूस करने के लिए पर्याप्त स्मार्ट है कि डिफ़ॉल्ट निर्माण कुछ भी नहीं करता है, इसलिए यह बफर के ऊपर से गुजरता है।

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

push_backसमाधान भी तो यह की तुलना में धीमी बनी हुई है, fencepost जाँच है, जो इसे धीमा कर देती है नीचे करता हैmalloc संस्करण।

लाइव उदाहरण (मैंने टाइमर को भी बदल दिया chrono::high_resolution_clock)।

ध्यान दें कि यदि आपके पास एक संरचना है जिसे आमतौर पर आरंभीकरण की आवश्यकता होती है, लेकिन आप इसे अपने बफ़र को बढ़ने के बाद संभालना चाहते हैं, तो आप इसे कस्टम std::vectorआवंटनकर्ता के साथ कर सकते हैं । यदि आप इसे और अधिक सामान्य स्थिति में ले जाना चाहते हैं, तो std::vectorमेरा मानना ​​है कि सावधान उपयोग allocator_traitsऔर ओवरराइडिंग को ==बंद कर सकता है, लेकिन अनिश्चित है।


यह भी देखना दिलचस्प होगा कि यहाँ emplace_backबनाम कैसे होता push_backहै।
डैनियल

1
मैं आपके परिणामों को पुन: पेश नहीं कर सकता। अपने कोड clang++ -std=c++11 -O3को संकलित करना UseArray completed in 2.02e-07 secondsऔर है UseVector completed in 1.3026 seconds। मैंने एक UseVectorEmplaceBackसंस्करण भी जोड़ा है जो लगभग है। 2.5x जितनी तेजी से UseVectorPushBack
डैनियल

1
@daniel ऑड्स ऑप्टिमाइज़र हैं, जो ऐरे संस्करण से सब कुछ हटा देते हैं। हमेशा माइक्रो बेंचमार्क के साथ एक जोखिम।
यक्क - एडम नेवरामोंट

4
हाँ, आप सही कह रहे हैं, बस असेंबली (या इसकी कमी) को देखा है .. शायद इसे 6448514x के अंतर को देखते हुए सोचा जाना चाहिए! मुझे आश्चर्य है कि वेक्टर संस्करण एक ही अनुकूलन क्यों नहीं बना सकता है .. ऐसा इसलिए होता है यदि आकार के बजाय आयामों के साथ निर्माण किया जाता है।
डैनियल

34

निष्पक्ष होने के लिए, आप C ++ कार्यान्वयन की तुलना C कार्यान्वयन से नहीं कर सकते हैं, क्योंकि मैं आपके मॉलॉक संस्करण को कॉल करूंगा। मॉलोक ऑब्जेक्ट नहीं बनाता है - यह केवल कच्ची मेमोरी आवंटित करता है। तब आप उस मेमोरी को ऑब्जेक्ट के रूप में मानते हैं बिना कंस्ट्रक्टर को कॉल किए खराब C ++ (संभवतः अमान्य है - मैं इसे भाषा वकीलों के लिए छोड़ दूँगा)।

उस ने कहा, बस मॉलॉक को बदलने new Pixel[dimensions*dimensions]और मुक्त करने delete [] pixelsके लिए पिक्सेल के सरल कार्यान्वयन के साथ बहुत फर्क नहीं पड़ता है जो आपके पास है। मेरे बॉक्स पर परिणाम (E6600, 64-बिट):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

लेकिन थोड़े बदलाव के साथ, टेबल पलट जाते हैं:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

इस तरह संकलित:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

हमें बहुत अलग परिणाम मिलते हैं:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

Pixel, std :: वेक्टर के लिए एक गैर-इनबिल्ड कंस्ट्रक्टर के साथ अब एक कच्चा ऐरे को धड़कता है।

ऐसा प्रतीत होता है कि std :: वेक्टर और std के माध्यम से आवंटन की जटिलता: आवंटनकर्ता को एक साधारण के रूप में प्रभावी रूप से अनुकूलित करने के लिए बहुत अधिक है new Pixel[n]। हालाँकि, हम देख सकते हैं कि समस्या बस आवंटन के साथ है न कि सदिश अभिगम परीक्षण के कुछ युग्मों को जोड़कर वेक्टर / सरणी बनाने के लिए एक बार लूप के बाहर ले जाकर:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

तथा

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

अब हमें ये परिणाम मिले:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

हम इससे जो सीख सकते हैं, वह यह है कि std :: वेक्टर उपयोग के लिए एक कच्चे सरणी के बराबर है, लेकिन यदि आपको वेक्टर / सरणी को कई बार बनाने और हटाने की आवश्यकता है, तो एक जटिल ऑब्जेक्ट बनाने में अधिक समय लगेगा जो कि एक साधारण सरणी बनाता है। जब एलिमेंट का कंस्ट्रक्टर इनबिल्ड नहीं होता है। मुझे नहीं लगता कि यह बहुत आश्चर्यजनक है।


3
आपके पास अभी भी एक अंतर्निर्मित कंस्ट्रक्टर है - कॉपी कंस्ट्रक्टर।
बेन वोइगट

26

इसके साथ प्रयास करें:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

मुझे सरणी के साथ लगभग वैसा ही प्रदर्शन मिलता है।

इसके बारे vectorमें बात यह है कि यह एक सरणी से बहुत अधिक सामान्य उपकरण है। और इसका मतलब है कि आपको विचार करना होगा कि कैसे आपको यह आप इसका उपयोग करते हैं। यह बहुत से अलग-अलग तरीकों से उपयोग किया जा सकता है, कार्यक्षमता प्रदान करता है जो एक सरणी में भी नहीं है। और यदि आप अपने उद्देश्य के लिए "गलत" का उपयोग करते हैं, तो आप बहुत अधिक ओवरहेड का उपयोग करते हैं, लेकिन यदि आप इसे सही तरीके से उपयोग करते हैं, तो यह आमतौर पर एक शून्य-ओवरहेड डेटा संरचना है। इस मामले में, समस्या यह है कि आपने अलग से वेक्टर को इनिशियलाइज़ किया है (जिससे सभी तत्व अपने डिफ़ॉल्ट ctor कहलाते हैं), और फिर प्रत्येक तत्व को अलग-अलग सही मान के साथ ओवरराइट कर दें। जब आप किसी सरणी के साथ एक ही काम करते हैं, तो कंपाइलर के लिए उसका अनुकूलन करना बहुत कठिन होता है। यही कारण है कि वेक्टर एक निर्माता प्रदान करता है जो आपको वास्तव में ऐसा करने देता है: ।NX

और जब आप इसका उपयोग करते हैं, तो वेक्टर एक सरणी के रूप में तेज होता है।

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

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


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

1
का उपयोग करना new[]वही डिफ़ॉल्ट निर्माण vector.resize()करता है जो करता है, फिर भी यह बहुत तेज है। new[]+ इनर लूप की गति vector.resize()+ इनर लूप जैसी ही होनी चाहिए , लेकिन ऐसा नहीं है, यह लगभग दोगुना है।
जॉन कुगेलमैन

@ जॉन: यह एक निष्पक्ष तुलना है। मूल कोड में, सरणी के साथ आवंटित किया जाता है malloc, जो प्रारंभ नहीं करता है या निर्माण कुछ भी तो यह है प्रभावी रूप से एक भी पास एल्गोरिथ्म बस मेरी तरह vectorनमूना। और new[]जवाब के रूप में जाहिर है कि दोनों को दो पास की आवश्यकता होती है, लेकिन new[]मामले में, कंपाइलर उस अतिरिक्त ओवरहेड को दूर करने में सक्षम होता है, जो कि vectorमामले में ऐसा नहीं करता है । लेकिन मैं यह नहीं देखता कि यह दिलचस्प क्यों है कि सबोप्टीमल मामलों में क्या होता है। यदि आप प्रदर्शन की परवाह करते हैं, तो आप उस तरह का कोड नहीं लिखते हैं।
jalf

@ जॉन: दिलचस्प टिप्पणी। अगर मैं पूरे सरणी को धुंधला करना चाहता था, तो मुझे लगता है कि सरणी फिर से इष्टतम समाधान है - क्योंकि मैं vector::resize()मुझे बिना किसी बेकार के निर्माणकर्ताओं को बुलाए समय बर्बाद करने के लिए स्मृति का एक आकस्मिक हिस्सा देने के लिए नहीं कह सकता ।
kizzx2

@ kizzx2: हाँ और नहीं। एक सरणी सामान्य रूप से C ++ में भी प्रारंभिक होती है। C में, आप उपयोग करेंगे mallocजो आरंभीकरण नहीं करता है, लेकिन यह C ++ में गैर-पीओडी प्रकारों के साथ काम नहीं करेगा। तो सामान्य मामले में, एक C ++ सरणी सिर्फ उतना ही बुरा होगा। शायद सवाल यह है कि यदि आप अक्सर इस ब्लटिंग का प्रदर्शन करने जा रहे हैं, तो क्या आप उसी सरणी / वेक्टर का पुन: उपयोग नहीं करेंगे? और यदि आप ऐसा करते हैं, तो आप केवल "बेकार कंस्ट्रक्टर्स" की लागत का भुगतान एक बार, बहुत शुरुआत में करते हैं। वास्तविक ब्लिटिंग सब के बाद उतनी ही तेजी से होता है।
जलफ

22

जब मैंने पहली बार आपके कोड को देखा था, तो यह तुलनात्मक रूप से उचित नहीं था; मैंने निश्चित रूप से सोचा था कि आप सेब के साथ सेब की तुलना नहीं कर रहे थे। तो मैंने सोचा, चलो सभी परीक्षणों पर निर्माणकर्ता और विध्वंसक मिलें; और फिर तुलना करें।

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

मेरे विचार थे, कि इस सेटअप के साथ, वे बिल्कुल समान होना चाहिए । यह पता चला है, मैं गलत था।

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

तो यह 30% प्रदर्शन हानि भी क्यों हुई? एसटीएल में हेडर में सब कुछ है, इसलिए कंपाइलर के लिए यह संभव होना चाहिए कि वह सब कुछ समझ सके।

मेरे विचार थे कि यह है कि कैसे लूप डिफ़ॉल्ट कंस्ट्रक्टर के सभी मूल्यों को इनिशियलाइज़ करता है। इसलिए मैंने एक परीक्षण किया:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

परिणाम मुझे संदेह था:

Default Constructed: 1
Copy Constructed: 300

यह स्पष्ट रूप से मंदी का स्रोत है, तथ्य यह है कि वेक्टर डिफ़ॉल्ट रूप से निर्मित ऑब्जेक्ट से तत्वों को इनिशियलाइज़ करने के लिए कॉपी कंस्ट्रक्टर का उपयोग करता है।

इसका मतलब है, कि वेक्टर के निर्माण के दौरान निम्नलिखित छद्म संचालन क्रम हो रहा है:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

जो, संकलक द्वारा बनाई गई अंतर्निहित कॉपी कंस्ट्रक्टर के कारण निम्नलिखित के लिए विस्तारित है:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

डिफ़ॉल्ट तो Pixel, अन-initialised बनी हुई है, जबकि बाकी initialised कर रहे हैं डिफ़ॉल्ट के साथ Pixelकी अन-initialised मूल्यों।

साथ वैकल्पिक स्थिति की तुलना में New[]/ Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

वे सभी अपने आरंभिक मूल्यों पर छोड़ दिए जाते हैं, और अनुक्रम पर दोहरे पुनरावृत्ति के बिना।

इस जानकारी के साथ सशस्त्र, हम इसे कैसे परख सकते हैं? आइए अंतर्निहित कॉपी कंस्ट्रक्टर को ओवर-राइट करने का प्रयास करें।

Pixel(const Pixel&) {}

और परिणाम?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

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

किसी भी मामले में, एसटीएल कार्यान्वयन किसी अज्ञात कारण से धीमा नहीं है, यह सिर्फ वही करता है जो आप पूछते हैं; उम्मीद है कि आप बेहतर जानते होंगे।


3
हम (आप और मैं और यहां के अन्य स्मार्ट लोग) की मस्ती को देखते हुए, एसटीएल की नकल की "उम्मीद" वास्तव में एक मांग है: पी मूल रूप से, हम अतिशयोक्ति कर सकते हैं और निष्कर्ष निकाल सकते हैं कि यह आशा करता है कि मैंने इसके सभी स्रोत को पढ़ा और विश्लेषण किया है। कोड। वैसे भी: P
kizzx2

1
Awsome! वीएस 2013 में यह वेक्टर ऐरे की तुलना में तेजी से बना। हालांकि ऐसा लगता है कि प्रदर्शन महत्वपूर्ण प्रणालियों के लिए आपको प्रभावी ढंग से इसका उपयोग करने में सक्षम होने के लिए एसटीएल का परीक्षण करने की आवश्यकता है।
रोज़िना

7

चेक किए गए पुनरावृत्तियों और रिलीज़ मोड में निर्माण को अक्षम करने का प्रयास करें । आपको बहुत अधिक प्रदर्शन अंतर नहीं देखना चाहिए।


1
कोशिश की #define _SECURE_SCL 0। यह UseVectorलगभग 4 सेकंड ( gccनीचे के समान ) बना है लेकिन फिर भी यह दो बार धीमा है।
kizzx2

यह लगभग निश्चित रूप से कारण है। Microsoft ने हमें डिबग और रिलीज़ दोनों के लिए डिफ़ॉल्ट रूप से इटैटर डिबगिंग पर अनुग्रहित किया। हमने 2003 से 2008 तक अपग्रेड करने के बाद बड़े पैमाने पर मंदी का मूल कारण पाया। निश्चित रूप से विजुअल स्टूडियो के सबसे खतरनाक चरों में से एक।
डग टी।

2
@ kizzx2 को निष्क्रिय करने के लिए एक और मैक्रो है: HAS_ITERATOR_DEBUGGING या कुछ ऐसे।
डग टी।

@ मर्टिन और मेरे जवाबों के अनुसार, gcc समान पैटर्न दिखाता है, यहाँ तक कि अनुकूलन पर भी -O3
जॉन कुगेलमैन

1
@ डग: डॉक को देखते हुए, मुझे लगता _HAS_ITERATOR_DEBUGGINGहै कि रिलीज़ बिल्ड में अक्षम है: msdn.microsoft.com/en-us/library/aa985939(VS.80).aspx
kizzx2

4

जीएनयू की एसटीएल (और अन्य), दी गई vector<T>(n), डिफ़ॉल्ट एक प्रोटोटाइप ऑब्जेक्ट का निर्माण करती है T()- कंपाइलर खाली कंस्ट्रक्टर को दूर कर देगा - लेकिन फिर जो कुछ कचरा हुआ है उसकी एक प्रतिलिपि अब मेमोरी के पते में आरक्षित है जो एसटीएलएल द्वारा लिया गया है __uninitialized_fill_n_aux, जो वेक्टर में डिफ़ॉल्ट मानों के रूप में उस ऑब्जेक्ट की पॉप्युलेटिंग कॉपी करता है। तो, "मेरे" एसटीएल निर्माण लूपिंग नहीं है, लेकिन फिर लूप / कॉपी का निर्माण करना है। यह सहज ज्ञान युक्त है, लेकिन मुझे याद रखना चाहिए क्योंकि मैंने हाल ही में स्टैकओवरफ्लो प्रश्न पर इस बिंदु के बारे में टिप्पणी की थी: संदर्भ गणना की गई वस्तुओं आदि के लिए निर्माण / प्रतिलिपि अधिक कुशल हो सकती है।

इसलिए:

vector<T> x(n);

या

vector<T> x;
x.resize(n);

है - कई एसटीएल कार्यान्वयन पर - कुछ इस तरह है:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

समस्या यह है कि संकलक ऑप्टिमाइज़र की वर्तमान पीढ़ी इस अंतर्दृष्टि से काम नहीं करती है कि अस्थायी असंवैधानिक कचरा है, और लूप और डिफ़ॉल्ट कॉपी कंस्ट्रक्टर इनवोकेशन का अनुकूलन करने में विफल है। आप विश्वसनीय रूप से तर्क दे सकते हैं कि कंपाइलर इसे पूरी तरह से दूर नहीं कर सकते हैं, क्योंकि ऊपर लिखने वाले प्रोग्रामर को एक उचित उम्मीद है कि सभी ऑब्जेक्ट्स लूप के बाद समान होंगे, भले ही कचरा ('समान' / ऑपरेटर == के बारे में सामान्य रूप से चेतावनी) memcmp / ऑपरेटर = आदि लागू होते हैं)। संकलक से std :: वेक्टर <> या डेटा के बाद के उपयोग में कोई अतिरिक्त अंतर्दृष्टि की उम्मीद नहीं की जा सकती है जो इस अनुकूलन को सुरक्षित बनाने का सुझाव देगा।

यह अधिक स्पष्ट, प्रत्यक्ष कार्यान्वयन के साथ विपरीत हो सकता है:

for (int i = 0; i < n; ++i)
    x[i] = T();

जिसे हम कंपाइलर से ऑप्टिमाइज़ करने की उम्मीद कर सकते हैं।

वेक्टर के व्यवहार के इस पहलू के औचित्य के बारे में थोड़ा और स्पष्ट होने के लिए, विचार करें:

std::vector<big_reference_counted_object> x(10000);

स्पष्ट रूप से यह एक बड़ा अंतर है यदि हम 10000 स्वतंत्र वस्तुओं बनाम 10000 को एक ही डेटा संदर्भित करते हैं। एक उचित तर्क है कि आकस्मिक सी ++ उपयोगकर्ताओं को गलती से बचाने का लाभ कुछ महंगा महंगा हार्ड-टू-ऑप्टिमाइज़ कॉपी निर्माण की बहुत छोटी वास्तविक लागत है।

मूल उत्तर (संदर्भ / टिप्पणियों की समझ बनाने के लिए): कोई मौका नहीं। वेक्टर एक सरणी के रूप में तेजी से होता है, कम से कम यदि आप समझदारी से अंतरिक्ष आरक्षित करते हैं। ...


6
मैं वास्तव में इस जवाब को किसी के लिए थोड़ा भी उपयोगी होने का औचित्य नहीं बता सकता। मुझे उम्मीद है कि मैं दो बार डाउनवोट हो सकता हूं।
kizzx2 2

-1, वहाँ kizzx2 पर मेरा समर्थन जाता है। सदिश कभी भी अतिरिक्त विशेषता के कारण सरणी के रूप में उपवास के लिए नहीं जा रहा है, ब्रह्मांड का शासन, सब कुछ एक कीमत है!
YeenFei

आपको याद आ रहा है, टोनी ... यह एक कृत्रिम बेंचमार्क का एक उदाहरण है, लेकिन यह साबित करता है कि यह किस उद्देश्य को पूरा करता है।
पोटाटोज़वाटर

गुलाब हरे हैं, violets नारंगी हैं, नीचे की कड़वी हैं, लेकिन जवाब उन्हें भीख देता है।
पावेल मिनेव

3

मार्टिन यॉर्क का जवाब मुझे परेशान करता है क्योंकि ऐसा लगता है कि कालीन के नीचे प्रारंभिक समस्या को ब्रश करने का प्रयास है। लेकिन वह प्रदर्शन समस्याओं के स्रोत के रूप में निरर्थक डिफ़ॉल्ट निर्माण की पहचान करने के लिए सही है।

[संपादित करें: मार्टिन का जवाब अब डिफ़ॉल्ट निर्माणकर्ता को बदलने का सुझाव नहीं देता है]

हाथ में तत्काल समस्या के लिए, आप निश्चित रूप से vector<Pixel>कॉटर के 2-पैरामीटर संस्करण को कॉल कर सकते हैं :

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

यह काम करता है यदि आप एक निरंतर मूल्य के साथ आरंभ करना चाहते हैं, जो एक सामान्य मामला है। लेकिन अधिक सामान्य समस्या यह है: आप एक निरंतर मूल्य की तुलना में अधिक जटिल कुछ के साथ कुशलता से कैसे आरंभ कर सकते हैं?

इसके लिए आप एक का उपयोग कर सकते हैं back_insert_iterator, जो कि एक इटरेटर एडेप्टर है। यहाँ ints के वेक्टर के साथ एक उदाहरण दिया गया है , हालाँकि सामान्य विचार Pixelएस के लिए भी काम करता है :

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

वैकल्पिक रूप से आप इसके बजाय copy()या उपयोग कर सकते हैं ।transform()generate_n()

नकारात्मक पक्ष यह है कि प्रारंभिक मूल्यों के निर्माण के तर्क को एक अलग वर्ग में ले जाने की जरूरत है, जो कि इन-प्लेस होने से कम सुविधाजनक है (हालांकि C ++ 1x में लैम्ब्डा इसे बहुत अच्छा बनाते हैं)। इसके अलावा, मुझे उम्मीद है कि यह अभी भी एक malloc()-आधारित गैर-एसटीएल संस्करण के रूप में तेज़ नहीं होगा , लेकिन मुझे उम्मीद है कि यह करीब होगा, क्योंकि यह केवल प्रत्येक तत्व के लिए एक निर्माण करता है।


2

वेक्टर वाले इसके अतिरिक्त पिक्सेल निर्माता कह रहे हैं।

प्रत्येक लगभग एक मिलियन ctor रन बना रहा है जो आप टाइम कर रहे हैं।

संपादित करें: फिर बाहरी 1 ... 1000 लूप है, ताकि एक अरब ctor कॉल करें!

2 संपादित करें: यह UseArray मामले के लिए disassembly को देखना दिलचस्प होगा। एक ऑप्टिमाइज़र पूरी चीज़ को अनुकूलित कर सकता है, क्योंकि सीपीयू को जलाने के अलावा इसका कोई प्रभाव नहीं है।


आप सही हैं, लेकिन सवाल यह है: इन व्यर्थ ctor कॉल को कैसे बंद किया जा सकता है? यह गैर-एसटीएल दृष्टिकोण के लिए आसान है, लेकिन एसटीएल रास्ते के लिए मुश्किल / बदसूरत है।
j_random_hacker

1

यहां बताया गया है कि push_backवेक्टर में काम करने का तरीका क्या है:

  1. सदिश आरम्भ होने पर X राशि का स्थान आवंटित करता है।
  2. जैसा कि नीचे बताया गया है कि यह जाँचता है कि आइटम के लिए वर्तमान अंतर्निहित सरणी में कोई जगह है या नहीं।
  3. यह पुश_बैक कॉल में आइटम की एक प्रति बनाता है।

push_backएक्स आइटम कॉल करने के बाद :

  1. वेक्टर एक दूसरे सरणी में अंतरिक्ष की kX राशि को पुनः प्राप्त करता है।
  2. यह पहले सरणी की प्रविष्टियों को दूसरे पर कॉपी करता है।
  3. पहले सरणी को त्यागता है।
  4. अब भंडारण के रूप में kX प्रविष्टियों तक पहुंचने तक दूसरे सरणी का उपयोग करता है।

दोहराएँ। यदि आप reservingइसकी निश्चित रूप से धीमी होने जा रही जगह नहीं कर रहे हैं । इससे भी बड़ी बात यह है कि अगर इस आइटम को कॉपी करना महंगा है तो 'पुश_बैक' जैसा आपको जिंदा खाने वाला है।

के रूप में vectorकी तुलना में सरणी बात है, मैं अन्य लोगों के साथ सहमत करने के लिए जा रहा हूँ। रिलीज़ में चलाएं, ऑप्टिमाइज़ेशन चालू करें, और कुछ और फ़्लैग में रखें, ताकि Microsoft पर मित्रवत लोग # @% $ ^ इसे हां तक ​​न करें।

एक और बात, अगर आपको आकार बदलने की आवश्यकता नहीं है, तो Boost.Array का उपयोग करें।


मैं समझता हूं कि जब यह शब्द पोस्ट किया जाता है तो लोग कोड का एक गुच्छा पढ़ना पसंद नहीं करते हैं। लेकिन मैंने जैसा इस्तेमाल किया reserve, वैसा करना चाहिए।
kizzx2

माफ करना, मैं चूक गया। क्या और कुछ नहीं था जो मैंने वहाँ रखा था?
गेहूं 3

push_backलगातार समय परिशोधन किया है। ऐसा लगता है कि आप एक O (N) प्रक्रिया का वर्णन कर रहे हैं। (चरण 1 और 3 पूरी तरह से जगह से बाहर लगते हैं।) push_backओपी के लिए क्या धीमा पड़ता है, यह देखने के लिए कि क्या reallocation होने की आवश्यकता है, यह देखने के लिए रेंज चेक है, पॉइंटर्स को अपडेट करना, NULL के अंदर प्लेसमेंट के खिलाफ चेक new, और अन्य छोटी चीजें जो सामान्य रूप से डूब जाती हैं। कार्यक्रम का वास्तविक कार्य।
पोटाटोज़वाटर

यह धीमा होने वाला है reserveक्योंकि इसके साथ अभी भी उस चेक को बनाना है (चाहे इसे वास्तविक बनाने की आवश्यकता हो) push_back
पावेल मिनेव

सभी अच्छे अंक। मैं एक ओ (एन) प्रक्रिया की तरह ध्वनियों का वर्णन कर रहा हूं, लेकिन यह अच्छी तरह से नहीं है। ज्यादातर लोग जानते हैं कि मुझे समझ में नहीं आता है कि vectorयह कैसे कार्यक्षमता का आकार बदलता है, यह सिर्फ "जादू" है। यहाँ, मैं इसे थोड़ा और स्पष्ट करता हूँ।
गेहूँ

1

कुछ प्रोफाइलर डेटा (पिक्सेल को 32 बिट्स से संरेखित किया गया है):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

बकवास

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

इन allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

सरणी

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

ओवरहेड का ज्यादातर हिस्सा कॉपी कंस्ट्रक्टर में है। उदाहरण के लिए,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

यह एक सरणी के रूप में एक ही प्रदर्शन है।


2
दुर्भाग्य से, आपके द्वारा दिए गए "समाधान" के बाद, pixels.size()टूट जाएगा।
kizzx2

1
यह गलत है, आप रिजर्व को कॉल नहीं कर सकते हैं और फिर तत्वों का उपयोग कर सकते हैं, आपको अभी भी आइटम जोड़ने के लिए push_back का उपयोग करना होगा
paulm

1

मेरा लैपटॉप लेनोवा जी 770 (4 जीबी रैम) है।

ओएस विंडोज 7 64-बिट (लैपटॉप के साथ एक) है

संकलक न्यूनतम 4.6.1 है।

IDE कोड है :: ब्लॉक

मैं पहले पोस्ट के स्रोत कोड का परीक्षण करता हूं।

परिणाम

O2 अनुकूलन

UseArray ने 2.841 सेकंड में पूरा किया

UseVector 2.548 सेकंड में पूरा हुआ

UseVectorPushBack 11.95 सेकंड में पूरा हुआ

पूरी बात 17.342 सेकंड में पूरी हुई

सिस्टम ठहराव

O3 अनुकूलन

UseArray 1.452 सेकंड में पूरा हुआ

UseVector 2.514 सेकंड में पूरा हुआ

UseVectorPushBack 12.967 सेकंड में पूरा हुआ

पूरी बात 16.937 सेकंड में पूरी हुई

ऐसा लगता है कि वेक्टर का प्रदर्शन O3 अनुकूलन के तहत बदतर है।

यदि आप लूप को बदल देते हैं

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

O2 और O3 के तहत सरणी और वेक्टर की गति लगभग समान है।


यहां तक ​​कि मैं मॉलॉक को नए में बदल देता हूं, ओ 3 के तहत पहले परीक्षण मामले में, वेक्टर का प्रदर्शन अभी भी सरणी की तुलना में धीमा है। लेकिन जब आप असाइन किए गए मान (255, 0, 0) से (i, i, i), के प्रदर्शन को बदलते हैं, तो वेक्टर और ऐरे लगभग O2 और O3 के समान हैं, यह बहुत अजीब है
StereoMatching

क्षमा करें, मैं हटाने के लिए स्वतंत्र बदलना भूल गया हूँ। हटाने के लिए स्वतंत्र रूप से बदलने के बाद, वेक्टर और सरणी के O3 के तहत प्रदर्शन अब समान हैं, ऐसा लगता है कि आवंटन मुख्य कारण है?
स्टीरियो

1

एक बेहतर बेंचमार्क (मुझे लगता है ...), अनुकूलन के कारण संकलक कोड को बदल सकते हैं, आवंटित वैक्टर / सरणियों के बीज़हाउस परिणाम कहीं भी उपयोग नहीं किए जाते हैं। परिणाम:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

संकलक:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

सी पी यू:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

और कोड:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

मैंने कुछ व्यापक परीक्षण किए जो मैं अभी कुछ समय के लिए करना चाहता था। इसे भी शेयर करें।

यह मेरी दोहरी बूट मशीन i7-3770, 16GB Ram, x86_64, विंडोज 8.1 पर और Ubuntu 16.04 पर है। अधिक जानकारी और निष्कर्ष, नीचे टिप्पणी। MSVS 2017 और g ++ (Windows और Linux पर दोनों) का परीक्षण किया।

परीक्षण कार्यक्रम

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

परिणाम

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

टिप्पणियाँ

  • 10 रनों के औसत से इकट्ठा।
  • मैंने शुरू में परीक्षण std::sort()भी किया था (आप इसे टिप्पणी कर सकते हैं) लेकिन बाद में उन्हें हटा दिया क्योंकि कोई महत्वपूर्ण रिश्तेदार अंतर नहीं थे।

मेरे निष्कर्ष और रिमार्क्स

  • गौर करें कि ग्लोबल सी-स्टाइल ऐरे को ढेर सी-स्टाइल ऐरे के रूप में लगभग कितना समय लगता है
  • सभी परीक्षणों में से मैंने std::arrayलगातार रन के बीच भिन्नता में एक उल्लेखनीय स्थिरता देखी , जबकि अन्य विशेष रूप से एसटीडी :: डेटा संरचनाएं तुलनात्मक रूप से विविध रूप से
  • O3 अनुकूलन में कोई उल्लेखनीय समय अंतर नहीं दिखा
  • Windows cl (no -O2) और g ++ (Win / Linux no -O2, no -march = native) पर ऑप्टिमाइज़ेशन हटाने से कई बार SIGNIFICANTLY बढ़ जाता है। विशेष रूप से एसटीडी :: डेटा संरचनाओं के लिए। जीवी ++ की तुलना में एमएसवीएस पर कुल मिलाकर अधिक समय, लेकिन std::arrayअनुकूलन के बिना विंडोज पर सी-स्टाइल सरणियां तेजी से होती हैं
  • g ++ माइक्रोसॉफ़्ट के कंपाइलर की तुलना में तेज़ कोड का उत्पादन करता है (जाहिर है कि यह विंडोज़ पर भी तेज़ चलता है)।

निर्णय

बेशक यह एक अनुकूलित बिल्ड के लिए कोड है। और जब से सवाल था std::vectorतब के बारे में हाँ! सादे सरणियों की तुलना में धीमी (अनुकूलित / अडॉप्टिमाइज्ड)। लेकिन जब आप एक बेंचमार्क कर रहे होते हैं, तो आप स्वाभाविक रूप से अनुकूलित कोड का उत्पादन करना चाहते हैं।

मेरे लिए शो का स्टार हालांकि रहा है std::array


0

सही विकल्पों के साथ, वैक्टर और सरणियां समान आसम उत्पन्न कर सकते हैं । इन मामलों में, वे निश्चित रूप से एक ही गति हैं, क्योंकि आपको एक ही निष्पादन योग्य फ़ाइल मिलती है।


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

1
@ kizzx2: आप का उपयोग करने के इस तरह के लंबाई करने के लिए जाना है कि Unconstructed वस्तुओं, एक अच्छी बात है, क्योंकि वह एक त्रुटि 99% है समय की (मैं निहायत underestimating हो सकता है)। मैंने अन्य उत्तरों को पढ़ा, और मुझे एहसास हुआ कि मैं आपकी विशिष्ट स्थिति को संबोधित नहीं करता (कोई ज़रूरत नहीं है, अन्य उत्तर सही हैं), लेकिन मैं अभी भी आपको इस उदाहरण के साथ प्रदान करना चाहता था कि वैक्टर और सरणियाँ बिल्कुल एक समान व्यवहार कर सकते हैं।

@ रेंजर: यह बहुत अच्छा है! लिंक के लिए धन्यवाद
kizzx2

0

जिस तरह से वेक्टर का उपयोग करने वाली कक्षाओं में आपके देखने की गति धीमी होती है वह भी मानक प्रकार जैसे int के साथ होती है। Heres एक बहुस्तरीय कोड:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

कोड से व्यवहार वेक्टर के तात्कालिकता को दर्शाता है जो कोड का सबसे लंबा हिस्सा है। एक बार जब आप उस बोतल गर्दन के माध्यम से मिलता है। बाकी कोड बेहद तेज चलता है। यह सच है कि आप कितने धागे पर चल रहे हैं।

वैसे शामिल की बिल्कुल पागल संख्या को नजरअंदाज करें। मैं एक परियोजना के लिए चीजों का परीक्षण करने के लिए इस कोड का उपयोग कर रहा हूं ताकि शामिल की संख्या बढ़ती रहे।


0

मैं केवल यह उल्लेख करना चाहता हूं कि वेक्टर (और smart_ptr) कच्ची सरणियों (और कच्चे पॉइंटर्स) के ऊपर सिर्फ एक पतली परत है। और वास्तव में निरंतर मेमोरी में एक वेक्टर का पहुंच समय सरणी से तेज है। निम्नलिखित कोड वेक्टर और सरणी को इनिशियलाइज़ और एक्सेस करने का परिणाम दिखाता है।

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

आउटपुट है:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

यदि आप इसे ठीक से उपयोग करते हैं तो गति लगभग समान होगी। (जैसा कि अन्य ने आरक्षित () या आकार बदलने के लिए उल्लेख किया है) ()।


0

खैर, क्योंकि वेक्टर :: आकार () सादे मेमोरी आवंटन (मॉलोक द्वारा) की तुलना में बहुत अधिक प्रसंस्करण करता है।

अपने कॉपी कंस्ट्रक्टर में एक ब्रेकपॉइंट लगाने की कोशिश करें (इसे परिभाषित करें ताकि आप ब्रेकप्वाइंट कर सकें!) और अतिरिक्त प्रसंस्करण समय हो जाता है।


0

मुझे कहना है कि मैं C ++ में विशेषज्ञ नहीं हूं। लेकिन कुछ प्रयोग परिणाम जोड़ने के लिए:

संकलन: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 वेक्टर.cpp

मशीन:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

ओएस:

2.6.32-642.13.1.el6.x86_64

आउटपुट:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

यहाँ केवल एक चीज मुझे अजीब लग रही है कि "UseCillstructor" की तुलना में "UseFillConstructor" का प्रदर्शन।

कोड:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

इसलिए अतिरिक्त "मूल्य" ने प्रदर्शन को काफी धीमा कर दिया, जो मुझे लगता है कि कंस्ट्रक्टर को कॉपी करने के लिए कई कॉल के कारण है। परंतु...

संकलित करें:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

आउटपुट:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

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


0

यह संकलक झंडे पर निर्भर करता है। यहाँ एक बेंचमार्क कोड है:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

विभिन्न अनुकूलन झंडे अलग जवाब देते हैं:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

आपके सटीक परिणाम अलग-अलग होंगे लेकिन यह मेरी मशीन पर काफी विशिष्ट है।


0

मेरे अनुभव में, कभी-कभी, बस कभी-कभी, vector<int>की तुलना में कई गुना धीमा हो सकता है int[]। एक बात का ध्यान रखें कि वैक्टर के वैक्टर बहुत विपरीत हैं int[][]। चूंकि स्मृति में तत्व संभवतः सन्निहित नहीं हैं। इसका मतलब है कि आप मुख्य एक के अंदर विभिन्न वैक्टर का आकार बदल सकते हैं, लेकिन सीपीयू तत्वों के साथ-साथ कैश के मामले में भी सक्षम नहीं हो सकता है int[][]

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.