जो तेज है: स्टैक आवंटन या हीप आवंटन


503

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

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

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

यह मुझे कुछ के रूप में प्रभावित करता है जो शायद बहुत संकलक पर निर्भर करेगा। इस परियोजना के लिए विशेष रूप से मैं PPC आर्किटेक्चर के लिए एक Metrowerks संकलक का उपयोग कर रहा हूं । इस संयोजन पर अंतर्दृष्टि सबसे सहायक होगी, लेकिन सामान्य तौर पर, जीसीसी और एमएसवीसी ++ के लिए, क्या मामला है? ढेर आवंटन के रूप में ढेर आवंटन के रूप में उच्च प्रदर्शन नहीं है? क्या कोई अंतर नहीं है? या अंतर इतने मिनट हैं कि यह व्यर्थ सूक्ष्म अनुकूलन बन जाता है।


11
मुझे पता है कि यह बहुत प्राचीन है, लेकिन विभिन्न प्रकार के आवंटन को प्रदर्शित करते हुए कुछ C / C ++ स्निपेट्स देखना अच्छा होगा।
जोसेफ वीसमैन

42
आपकी गाय का ऑर्केनिक बहुत ही अज्ञानी है, लेकिन अधिक महत्वपूर्ण वह खतरनाक है क्योंकि वह उन चीजों के बारे में आधिकारिक दावा करता है जिनके बारे में वह बहुत अनभिज्ञ है। अपनी टीम से ऐसे लोगों को जितनी जल्दी हो सके बाहर करें।
जिम बाल्टर

5
ध्यान दें कि ढेर आमतौर पर स्टैक की तुलना में बहुत बड़ा है। यदि आपको बड़ी मात्रा में डेटा आवंटित किया जाता है, तो आपको वास्तव में इसे ढेर पर रखना होगा, अन्यथा ओएस से स्टैक का आकार बदल दें।
पॉल ड्रेपर

1
सभी अनुकूलन तब तक होते हैं, जब तक कि आपके पास डिफ़ॉल्ट व्यर्थ माइक्रो-अनुकूलन द्वारा अन्यथा बेंचमार्क या जटिलता तर्क नहीं होते हैं।
ब्योर्न लिंडक्विस्ट

2
मुझे आश्चर्य है कि अगर आपके सहकर्मी को ज्यादातर जावा या सी # अनुभव है। उन भाषाओं में, लगभग सब कुछ हूड के तहत हीप-आबंटित किया जाता है, जिससे ऐसी धारणा हो सकती है।
Cort Ammon

जवाबों:


493

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

इसके अलावा, ढेर बनाम ढेर केवल एक प्रदर्शन विचार नहीं है; यह आपको वस्तुओं के अपेक्षित जीवनकाल के बारे में बहुत कुछ बताता है।


211
और अधिक महत्वपूर्ण, स्टैक हमेशा गर्म होता है, आपके द्वारा प्राप्त की जाने वाली मेमोरी कैश में होने की संभावना है जो कि किसी भी ढेर से आवंटित मेमोरी की तुलना में अधिक है
Beno't

47
कुछ (ज्यादातर एम्बेडेड, जो मुझे पता है) आर्किटेक्चर पर, स्टैक को फास्ट-ऑन-मेमोरी मेमोरी (जैसे SRAM) में संग्रहीत किया जा सकता है। यह एक बड़ा अंतर बना सकता है!
जूल

38
क्योंकि स्टैक वास्तव में एक स्टैक है। जब तक यह इसके शीर्ष पर नहीं होता तब तक आप स्टैक द्वारा उपयोग की जाने वाली मेमोरी को मुक्त नहीं कर सकते। कोई प्रबंधन नहीं है, आप इस पर चीजों को धक्का या पॉप करते हैं। दूसरी ओर, ढेर स्मृति का प्रबंधन किया जाता है: यह कर्नेल को मेमोरी चंक्स के लिए पूछता है, हो सकता है कि उन्हें विभाजित करता है, उन्हें विलय करता है, उन्हें पुन: उपयोग करता है और उन्हें मुक्त करता है। स्टैक वास्तव में तेज और छोटे आवंटन के लिए है।
बेनोइट

24
@ स्पेसर क्योंकि ढेर ढेर की तुलना में बहुत छोटा है। यदि आप बड़े सरणियों का आवंटन करना चाहते हैं, तो आप उन्हें हीप पर बेहतर रूप से आवंटित करते हैं। यदि आप स्टैक पर एक बड़ा सरणी आवंटित करने का प्रयास करते हैं तो यह आपको एक स्टैक ओवरफ्लो देगा। C ++ में उदाहरण के लिए प्रयास करें: int t [100000000]; उदाहरण के लिए प्रयास करें [10000000] = 10; और फिर cout << t [10000000]; यह आपको एक स्टैक ओवरफ्लो देना चाहिए या बस काम नहीं करेगा और आपको कुछ भी नहीं दिखाएगा। लेकिन यदि आप ढेर पर सरणी आवंटित करते हैं: int * t = new int [100000000]; और एक ही ऑपरेशन के बाद करते हैं, यह काम करेगा क्योंकि हीप में इतने बड़े सरणी के लिए आवश्यक आकार है।
लिलियन ए। मोरारू

7
@Pacerier सबसे स्पष्ट कारण यह है कि स्टैक पर ऑब्जेक्ट्स उस ब्लॉक से बाहर निकलने की गुंजाइश से बाहर निकल जाते हैं जो उन्हें आवंटित किया गया है।
जिम बेल्टर

166

ढेर ज्यादा तेज है। यह शाब्दिक रूप से अधिकांश आर्किटेक्चर पर एकल निर्देश का उपयोग करता है, ज्यादातर मामलों में, उदाहरण के लिए x86 पर:

sub esp, 0x10

(जो स्टैक पॉइंटर को 0x10 बाइट्स से नीचे ले जाता है और इस तरह उन बाइट्स को एक वेरिएबल द्वारा उपयोग के लिए "आवंटित" करता है)।

बेशक, स्टैक का आकार बहुत, बहुत परिमित है, जैसा कि आप जल्दी से पता लगाएंगे कि क्या आप स्टैक आवंटन का उपयोग करते हैं या पुनरावर्तन करने की कोशिश करते हैं :-)

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

मेरे अंगूठे का नियम: अगर मुझे पता है कि मुझे संकलन-समय पर कुछ डेटा की आवश्यकता है , और यह आकार में कुछ सौ बाइट्स के तहत है, तो मैं इसे आवंटित करता हूं। नहीं तो मैं इसे आवंटित करता हूं।


20
एक निर्देश, और वह आमतौर पर स्टैक पर सभी वस्तुओं द्वारा साझा किया जाता है।
एमएसलेट्स

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

6
"डीक्लोकेशन" भी बहुत सरल है और एकल leaveनिर्देश के साथ किया जाता है ।
doc

15
यहां "छिपी हुई" लागत को ध्यान में रखें, खासकर पहली बार जब आप स्टैक का विस्तार करते हैं। ऐसा करने से पृष्ठ दोष हो सकता है, एक संदर्भ कर्नेल पर जाता है जिसे मेमोरी आवंटित करने के लिए कुछ काम करने की आवश्यकता होती है (या इसे सबसे खराब स्थिति में स्वैप से लोड करना पड़ता है)।
nos

2
कुछ मामलों में, आप इसे 0 निर्देशों के साथ भी आवंटित कर सकते हैं। यदि कुछ जानकारी के बारे में जाना जाता है कि कितने बाइट्स को आवंटित करने की आवश्यकता है, तो कंपाइलर उन्हें उसी समय अग्रिम में आवंटित कर सकते हैं जब यह अन्य स्टैक चर आवंटित करता है। उन मामलों में, आप कुछ भी भुगतान नहीं करते हैं!
कॉर्ट अमोन

120

ईमानदारी से, प्रदर्शन की तुलना करने के लिए एक कार्यक्रम लिखना तुच्छ है:

#include <ctime>
#include <iostream>

namespace {
    class empty { }; // even empty classes take up 1 byte of space, minimum
}

int main()
{
    std::clock_t start = std::clock();
    for (int i = 0; i < 100000; ++i)
        empty e;
    std::clock_t duration = std::clock() - start;
    std::cout << "stack allocation took " << duration << " clock ticks\n";
    start = std::clock();
    for (int i = 0; i < 100000; ++i) {
        empty* e = new empty;
        delete e;
    };
    duration = std::clock() - start;
    std::cout << "heap allocation took " << duration << " clock ticks\n";
}

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

एक अनुकूलन कंपाइलर नोटिस कर सकता है कि यह कोड कुछ भी नहीं करता है, और इसे सभी को अनुकूलित कर सकता है। यह सामान की तरह काम करने के लिए ऑप्टिमाइज़र का काम है, और ऑप्टिमाइज़र से लड़ना एक मूर्खता है।

मैं इस कोड को अनुकूलन के साथ संकलित करने की सलाह दूंगा क्योंकि वर्तमान में उपयोग में आने वाले प्रत्येक ऑप्टिमाइज़र को बेवकूफ बनाने का कोई अच्छा तरीका नहीं है या जो भविष्य में उपयोग में होगा।

जो कोई भी आशावादी को चालू करता है और फिर लड़ने के बारे में शिकायत करता है, उसे सार्वजनिक उपहास के अधीन होना चाहिए।

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

ऑप्टिमाइज़र के पास एक मिशन है जो मैं परीक्षण कर रहा हूँ कोड से छुटकारा पाने के लिए। मुझे ऑप्टिमाइज़र को चलाने के लिए कहने और फिर ऑप्टिमाइज़र को बेवकूफ बनाने की कोशिश करने का कोई कारण नहीं दिखता है। लेकिन अगर मैंने ऐसा करने में मूल्य देखा, तो मैं निम्नलिखित में से एक या अधिक काम करूंगा:

  1. एक डेटा सदस्य जोड़ें emptyऔर उस डेटा सदस्य को लूप में एक्सेस करें; लेकिन अगर मैं कभी भी डेटा सदस्य से पढ़ता हूं तो ऑप्टिमाइज़र निरंतर तह कर सकता है और लूप को हटा सकता है; अगर मैं कभी भी केवल डेटा सदस्य को लिखता हूं, तो आशावादी सभी को छोड़ सकता है, लेकिन लूप का बहुत अंतिम चलना। इसके अतिरिक्त, सवाल "ढेर आवंटन और डेटा एक्सेस बनाम हीप आवंटन और डेटा एक्सेस नहीं था।"

  2. घोषणा करें e volatile, लेकिन volatileअक्सर गलत तरीके से संकलित किया जाता है (पीडीएफ)।

  3. eलूप के अंदर का पता लें (और हो सकता है कि इसे एक वेरिएबल में असाइन किया externगया हो जो दूसरी फ़ाइल में घोषित और परिभाषित है)। लेकिन इस मामले में भी, संकलक नोटिस कर सकता है कि - स्टैक पर कम से कम - eहमेशा एक ही मेमोरी पते पर आवंटित किया जाएगा, और फिर ऊपर (1) की तरह लगातार तह करना। मुझे लूप के सभी पुनरावृत्तियों मिलते हैं, लेकिन वस्तु कभी भी आवंटित नहीं होती है।

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

मेरी मशीन पर, विंडोज पर जी ++ 3.4.4 का उपयोग करते हुए, मुझे 100000 से कम आवंटन के लिए स्टैक और हीप आवंटन दोनों के लिए "0 क्लॉक टिक" मिलते हैं, और फिर भी मुझे स्टैक आवंटन के लिए "0 क्लॉक टिक" और "15 क्लॉक टिक" मिलते हैं। "ढेर आवंटन के लिए। जब मैं 10,000,000 आवंटन को मापता हूं, तो स्टैक आवंटन 31 घड़ी टिक लेता है और हीप आवंटन 1562 घड़ी टिक लेता है।


हां, एक अनुकूलन करने वाला कंपाइलर खाली वस्तुओं को बनाने में सक्षम हो सकता है। अगर मैं सही तरीके से समझूं, तो यह पूरे पहले लूप को खत्म कर सकता है। जब मैंने १०,०००,००० स्टैक आवंटन के पुनरावृत्तियों को टकराया, तो ३१ घड़ी टिक गईं और हीप आवंटन १५६२ घड़ी टिक लिया। मुझे लगता है कि यह कहना सुरक्षित है कि निष्पादन योग्य को अनुकूलित करने के लिए जी ++ के बिना, जी ++ ने कंस्ट्रक्टरों को नहीं बताया।


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

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

#include <cstdio>
#include <chrono>

namespace {
    void on_stack()
    {
        int i;
    }

    void on_heap()
    {
        int* i = new int;
        delete i;
    }
}

int main()
{
    auto begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_stack();
    auto end = std::chrono::system_clock::now();

    std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());

    begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_heap();
    end = std::chrono::system_clock::now();

    std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());
    return 0;
}

प्रदर्शित करता है:

on_stack took 2.070003 seconds
on_heap took 57.980081 seconds

कमांड लाइन के साथ संकलित होने पर मेरे सिस्टम पर cl foo.cc /Od /MT /EHsc

आप गैर-अनुकूलित बिल्ड प्राप्त करने के लिए मेरे दृष्टिकोण से सहमत नहीं हो सकते हैं। यह ठीक है: जितना संभव हो, बेंचमार्क को मुफ्त में संशोधित करें। जब मैं अनुकूलन चालू करता हूँ, मुझे मिलता है:

on_stack took 0.000000 seconds
on_heap took 51.608723 seconds

इसलिए नहीं कि स्टैक आवंटन वास्तव में तात्कालिक है, बल्कि इसलिए क्योंकि कोई भी अर्ध-सभ्य संकलक यह नोटिस कर सकता है कि on_stackकुछ उपयोगी नहीं है और इसे अनुकूलित किया जा सकता है। मेरे लिनक्स लैपटॉप पर जीसीसी भी नोटिस on_heapकरता है जो कुछ भी उपयोगी नहीं करता है, और इसे दूर भी अनुकूलित करता है:

on_stack took 0.000003 seconds
on_heap took 0.000002 seconds

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

2
मुझे भी खुशी है कि प्रत्येक विकल्प लूप रन की संख्या बढ़ रही है (साथ ही जी ++ को ऑप्टिमाइज़ न करने का निर्देश?) ने महत्वपूर्ण परिणाम दिए हैं। इसलिए अब हमारे पास यह कहना कठिन है कि स्टैक अधिक तेज़ है। आपके प्रयासों के लिए धन्यवाद!
जो पिनेडा

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

3
मुझे बहुत देर हो चुकी है, लेकिन यह भी यहाँ ध्यान देने योग्य बात है कि ढेर आवंटन कर्नेल के माध्यम से स्मृति का अनुरोध करता है, इसलिए प्रदर्शन हिट भी दृढ़ता से कर्नेल की दक्षता पर निर्भर करता है। लिनक्स (लिनक्स 3.10.7-Gentoo # 2 SMP सितम्बर 4 18:58:21 एमडीटी 2013 x86_64) के साथ इस कोड का उपयोग करना, मानव संसाधन टाइमर के लिए संशोधित करने, और प्रत्येक पाश में 100 मिलियन पुनरावृत्तियों उपयोग करते हुए इस प्रदर्शन पैदावार: stack allocation took 0.15354 seconds, heap allocation took 0.834044 secondsके साथ -O0सेट, निर्माण लिनक्स हीप आवंटन केवल मेरे विशेष मशीन पर लगभग 5.5 के कारक पर धीमा है।
तैवे

4
अनुकूलन (डिबग बिल्ड) के बिना खिड़कियों पर यह डीबग हीप का उपयोग करेगा जो कि गैर डिबग हीप की तुलना में बहुत धीमा है। मुझे नहीं लगता कि इसका बुरा विचार आशावादी को "छल" करने का है। कंपाइलर लेखक स्मार्ट होते हैं, लेकिन कंपाइलर्स AI के नहीं होते हैं।
११’१४ बजे १:१४

30

एक दिलचस्प बात जो मैंने Xbox 360 क्सीनन प्रोसेसर पर स्टैक बनाम हीप एलोकेशन के बारे में सीखी, जो अन्य मल्टीकोर सिस्टम पर भी लागू हो सकती है, यह है कि हीप पर आवंटित करने से अन्य सभी कोरों को रोकने के लिए एक क्रिटिकल सेक्शन में प्रवेश किया जा सकता है, ताकि आवंटन न हो। 'संघर्ष नहीं। इस प्रकार, एक तंग पाश में, स्टैक एलोकेशन निश्चित आकार के सरणियों के लिए जाने का रास्ता था क्योंकि यह स्टालों को रोकता था।

यह विचार करने के लिए एक और स्पीडअप हो सकता है कि क्या आप मल्टीकोर / मल्टीकोर के लिए कोडिंग कर रहे हैं, जिसमें आपका स्टैक आवंटन केवल आपके स्कोप्ड फ़ंक्शन को चलाने वाले कोर द्वारा देखा जा सकेगा, और यह किसी अन्य कोर / सीपीयू को प्रभावित नहीं करेगा।


4
यह अधिकांश मल्टीकोर मशीनों का सच है, न कि केवल क्सीनन। यहां तक ​​कि सेल को भी करना पड़ता है क्योंकि आप उस PPU कोर पर दो हार्डवेयर थ्रेड चला रहे होंगे।
क्रेशवर्क्स

15
यह ढेर आवंटनकर्ता के (विशेष रूप से खराब) कार्यान्वयन का एक प्रभाव है। बेहतर ढेर आवंटनकर्ताओं को हर आवंटन पर ताला लगाने की आवश्यकता नहीं है।
क्रिस डोड

19

आप वस्तुओं के विशिष्ट आकार के लिए एक विशेष ढेर आवंटन लिख सकते हैं जो बहुत ही शानदार है। हालांकि, सामान्य ढेर आवंटन विशेष रूप से प्रदर्शन करने वाला नहीं है।

इसके अलावा मैं Torbjörn Gyllebring के साथ वस्तुओं के अपेक्षित जीवनकाल के बारे में सहमत हूं। अच्छी बात!


1
इसे कभी-कभी स्लैब आवंटन कहा जाता है।
बेनोइट

8

मुझे नहीं लगता कि स्टैक आवंटन और ढेर आवंटन आम तौर पर विनिमेय हैं। मुझे यह भी उम्मीद है कि सामान्य उपयोग के लिए दोनों का प्रदर्शन पर्याप्त है।

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

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

64-बिट ऑपरेटिंग सिस्टम पर थ्रेड स्टैक को काफी बड़ा करने के लिए पर्याप्त एड्रेस स्पेस है।


6

आमतौर पर स्टैक आवंटन में स्टैक पॉइंटर रजिस्टर से घटाना होता है। यह ढेर खोजने से ज्यादा तेज है।

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


मुझे नहीं लगता कि ढेर को "खोज" किया जाता है जब तक कि यह पृष्ठांकित न हो। बहुत यकीन है कि ठोस राज्य मेमोरी मल्टीप्लेक्स का उपयोग करती है और मेमोरी तक सीधी पहुंच प्राप्त कर सकती है, इसलिए रैंडम एक्सेस मेमोरी।
जो फिलिप्स

4
यहाँ एक उदाहरण है। कॉलिंग प्रोग्राम 37 बाइट्स आवंटित करने के लिए कहता है। लाइब्रेरी फ़ंक्शन कम से कम 40 बाइट्स के ब्लॉक की तलाश करता है। मुफ्त सूची में पहले ब्लॉक में 16 बाइट्स हैं। मुक्त सूची के दूसरे ब्लॉक में 12 बाइट्स हैं। तीसरे ब्लॉक में 44 बाइट्स हैं। पुस्तकालय उस बिंदु पर खोज करना बंद कर देता है।
विंडोज प्रोग्रामर

6

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


4

एक स्टैक की एक सीमित क्षमता है, जबकि एक ढेर नहीं है। एक प्रक्रिया या धागे के लिए विशिष्ट स्टैक लगभग 8K है। एक बार आवंटित होने के बाद आप आकार नहीं बदल सकते।

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

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


1
एक आधुनिक ओएस पर उपयोगकर्ता मोड स्टैक के लिए आवंटित वर्चुअल एड्रेस स्पेस की मात्रा डिफ़ॉल्ट रूप से कम से कम 64kB या उससे अधिक होने की संभावना है (विंडोज पर 1MB)। क्या आप कर्नेल स्टैक आकारों के बारे में बात कर रहे हैं?
bk1e

1
मेरी मशीन पर, एक प्रक्रिया के लिए डिफ़ॉल्ट स्टैक का आकार 8 एमबी है, न कि केबी। आपका कंप्यूटर कितना पुराना है?
ग्रेग रोजर्स

3

मुझे लगता है कि जीवनकाल महत्वपूर्ण है, और क्या आवंटित की जा रही चीज का निर्माण जटिल तरीके से किया जाना है। उदाहरण के लिए, लेन-देन चालित मॉडलिंग में, आपको आमतौर पर फ़ंक्शंस के संचालन के लिए फ़ील्ड्स के एक समूह के साथ लेन-देन संरचना में भरना और पास करना पड़ता है। उदाहरण के लिए OSCI SystemC TLM-2.0 मानक देखें।

ऑपरेशन के लिए कॉल के करीब स्टैक पर इन्हें आवंटित करने से भारी ओवरहेड हो जाता है, क्योंकि निर्माण महंगा है। जिस तरह से ढेर पर आवंटन करना और लेनदेन की वस्तुओं को पूलिंग या फिर एक सरल नीति जैसे "इस मॉड्यूल को केवल एक लेन-देन की आवश्यकता है" का पुन: उपयोग करना है।

यह प्रत्येक ऑपरेशन कॉल पर ऑब्जेक्ट को आवंटित करने से कई गुना तेज है।

कारण बस इतना है कि वस्तु का महंगा निर्माण और जीवन भर काफ़ी उपयोगी है।

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


3

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

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


3

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


3

स्टैक आवंटन एक युगल निर्देश है जबकि सबसे तेज आरटीओ हीप एलोकेटर जो मुझे ज्ञात है (टीएलएसएफ) 150 निर्देशों के आदेश पर औसतन उपयोग करता है। इसके अलावा स्टैक आवंटन के लिए लॉक की आवश्यकता नहीं होती है क्योंकि वे थ्रेड स्थानीय भंडारण का उपयोग करते हैं जो एक और विशाल प्रदर्शन जीत है। तो ढेर का आवंटन 2-3 परिमाण का तेज़ी से हो सकता है, यह इस बात पर निर्भर करता है कि आपका वातावरण कितना भारी है।

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


3

सी ++ भाषा के लिए विशिष्ट चिंताएं

सबसे पहले, C ++ द्वारा अनिवार्य "स्टैक" या "हीप" आवंटन नहीं है । यदि आप ब्लॉक स्कोप में स्वचालित वस्तुओं के बारे में बात कर रहे हैं, तो वे "आवंटित" भी नहीं हैं। (बीटीडब्ल्यू, सी में स्वचालित भंडारण की अवधि निश्चित रूप से "आवंटित" के समान नहीं है; उत्तरार्द्ध सी समानता में "गतिशील" है।) गतिशील रूप से आवंटित स्मृति मुक्त स्टोर पर है , जरूरी नहीं कि "ढेर" पर हो। उत्तरार्द्ध अक्सर (डिफ़ॉल्ट) कार्यान्वयन होता है

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

दूसरी ओर, नि: शुल्क स्टोर आवंटन निश्चित रूप से डिजाइन द्वारा "आवंटन" है। आईएसओ सी ++ नियमों के तहत, इस तरह के आवंटन को आवंटन फ़ंक्शन के कॉल द्वारा प्राप्त किया जा सकता है । हालाँकि, ISO C ++ 14 के बाद से, विशिष्ट मामलों में वैश्विक आवंटन फ़ंक्शन (यानी ) कॉल को मर्ज करने की अनुमति देने के लिए एक नया (गैर-अगर-अगर) नियम है ::operator new। इसलिए डायनामिक एलोकेशन ऑपरेशंस के हिस्से भी ऑटोमैटिक ऑब्जेक्ट्स के मामले की तरह नो-ऑप हो सकते हैं।

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

अन्य सभी चिंताएं C ++ के दायरे से बाहर हैं। फिर भी, वे अभी भी महत्वपूर्ण हो सकते हैं।

C ++ के कार्यान्वयन के बारे में

C ++, सक्रियण रिकॉर्ड्स या प्रथम-श्रेणी के निरंतरताओं के कुछ प्रकारों (जैसे प्रसिद्ध द्वारा call/cc) को उजागर नहीं करता है , सक्रियण रिकॉर्ड फ़्रेमों को सीधे हेरफेर करने का कोई तरीका नहीं है - जहां कार्यान्वयन को स्वचालित वस्तुओं को रखने की आवश्यकता होती है। एक बार अंतर्निहित कार्यान्वयन ("मूल" गैर-पोर्टेबल कोड, जैसे इनलाइन असेंबली कोड) के साथ कोई (गैर-पोर्टेबल) अंतर नहीं होता है, फ़्रेम के अंतर्निहित आवंटन का एक चूक काफी तुच्छ हो सकता है। उदाहरण के लिए, जब फंक्शन को इनलाइन किया जाता है, तो फ़्रेम को प्रभावी रूप से दूसरों में विलय किया जा सकता है, इसलिए यह दिखाने का कोई तरीका नहीं है कि "आवंटन" क्या है।

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

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

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

मेमोरी एक्सेस पर प्रभाव

सामान्य स्टैक आवंटन हमेशा नए फ्रेम को शीर्ष पर रखता है, इसलिए इसमें काफी अच्छा स्थान है। यह कैश के अनुकूल है। OTOH, फ्री स्टोर में बेतरतीब ढंग से आवंटित मेमोरी में ऐसी कोई संपत्ति नहीं है। आईएसओ सी ++ 17 के बाद से, पूल संसाधन टेम्पलेट प्रदान किए गए हैं <memory>। इस तरह के इंटरफ़ेस का प्रत्यक्ष उद्देश्य लगातार आवंटन के परिणामों को स्मृति में एक साथ बंद करने की अनुमति देना है। यह इस तथ्य को स्वीकार करता है कि यह रणनीति आमतौर पर समकालीन कार्यान्वयन के साथ प्रदर्शन के लिए अच्छी है, उदाहरण के लिए आधुनिक आर्किटेक्चर में कैश के अनुकूल होना। यह आवंटन के बजाय पहुंच के प्रदर्शन के बारे में है , हालांकि।

संगामिति

मेमोरी के समवर्ती उपयोग की अपेक्षा स्टैक और ढेर के बीच अलग-अलग प्रभाव हो सकते हैं। एक कॉल स्टैक आमतौर पर C ++ कार्यान्वयन में निष्पादन के एक धागे के स्वामित्व में होता है। OTOH, ढेर अक्सर एक प्रक्रिया में धागे के बीच साझा किए जाते हैं । इस तरह के ढेर के लिए, आवंटन और सौदे के कार्यों को डेटा रेस से साझा आंतरिक प्रशासनिक डेटा संरचना की रक्षा करना है। नतीजतन, आंतरिक आवंटन के संचालन के कारण ढेर आवंटन और डीलॉक्लेशन अतिरिक्त ओवरहेड हो सकते हैं।

अंतरिक्ष क्षमता

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

ढेर आवंटन की सीमाएं

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

सबसे पहले, आईएसओ सी ++ के साथ पोर्टेबल तरीके से रनटाइम पर निर्दिष्ट आकार के साथ स्टैक पर जगह आवंटित करने का कोई तरीका नहीं है। allocaजीएएल के वीएलए (चर-लंबाई सरणी) जैसे कार्यान्वयन द्वारा प्रदान किए गए एक्सटेंशन हैं , लेकिन उनसे बचने के लिए कारण हैं। (IIRC, लिनक्स स्रोत वीएलए के उपयोग को हाल ही में हटाता है।) (यह भी ध्यान दें कि आईएसओ C99 में VLA अनिवार्य है, लेकिन ISO C11 समर्थन को वैकल्पिक बनाता है।)

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

यदि स्टैक स्पेस बाहर निकलता है, तो स्टैक में बहुत अधिक ऑब्जेक्ट आवंटित किए जाते हैं, जो फ़ंक्शंस के बहुत सक्रिय कॉल या स्वचालित ऑब्जेक्ट के अनुचित उपयोग के कारण हो सकता है। ऐसे मामले बग के अस्तित्व का सुझाव दे सकते हैं, उदाहरण के लिए, सही निकास की स्थिति के बिना एक पुनरावर्ती फ़ंक्शन कॉल।

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


एसटीएल की तरह, कम और कम इन अवधारणाओं को अलग करने के लिए तैयार हैं। Cppcon2018 पर कई दोस्त भी heapअक्सर उपयोग करते हैं।
陳 陳

@ @ Amb "हीप" कुछ विशिष्ट कार्यान्वयन को ध्यान में रखते हुए अस्पष्ट हो सकता है, इसलिए यह शायद कभी-कभी ठीक है। यह बेमानी है "सामान्य तौर पर", हालांकि।
फ्रैंकएच

इंटरॉप क्या है?
陳 陳

@ @ 陳 मेरा मतलब C ++ स्रोत में शामिल "देशी" कोड इंटरऑपरेशन का कोई भी प्रकार है, उदाहरण के लिए, कोई भी इनलाइन असेंबली कोड। यह सी + + द्वारा कवर नहीं की गई मान्यताओं (एबीआई का) पर निर्भर करता है। COM इंटरॉप (कुछ विंडोज-विशिष्ट एबीआई पर आधारित) कमोबेश समान है, हालांकि यह ज्यादातर C ++ के लिए तटस्थ है।
फ्रैंकएच

2

इस तरह के अनुकूलन के बारे में एक सामान्य बिंदु है।

आपके द्वारा प्राप्त अनुकूलन उस समय की मात्रा के अनुपात में आनुपातिक है जो वास्तव में उस कोड में है।

यदि आप प्रोग्राम काउंटर का नमूना लेते हैं, तो आपको पता चलेगा कि यह अपना समय कहाँ बिताता है, और यह आमतौर पर कोड के एक छोटे हिस्से में होता है, और अक्सर पुस्तकालय की दिनचर्या में आपका कोई नियंत्रण नहीं होता है।

केवल तभी जब आप इसे अपनी वस्तुओं के ढेर-आवंटन में अधिक समय बिताते हुए पाते हैं, यह उन्हें ढेर-आवंटित करने के लिए काफ़ी तेज़ होगा।


2

ढेर आवंटन लगभग हमेशा हीप आवंटन की तुलना में तेज या तेज होगा, हालांकि यह ढेर आवंटन के लिए निश्चित रूप से संभव है कि केवल स्टैक आधारित आवंटन तकनीक का उपयोग करें।

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

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


मैं आपके पहले वाक्य से असहमत हूं।
जुआन

2

जैसा कि दूसरों ने कहा है, स्टैक आवंटन आम तौर पर बहुत तेज है।

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

उदाहरण के लिए, यदि आप स्टैक पर कुछ आवंटित करते हैं, और फिर कंटेनर में डालते हैं, तो ढेर पर आवंटित करना बेहतर होता है और पॉइंटर को कंटेनर में संग्रहीत किया जाता है (उदाहरण के लिए एक std :: share_ptr <>)। यदि आप मूल्य या अन्य समान परिदृश्यों से वस्तुओं को पास कर रहे हैं या लौटा रहे हैं तो वही बात सच है।

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


2
class Foo {
public:
    Foo(int a) {

    }
}
int func() {
    int a1, a2;
    std::cin >> a1;
    std::cin >> a2;

    Foo f1(a1);
    __asm push a1;
    __asm lea ecx, [this];
    __asm call Foo::Foo(int);

    Foo* f2 = new Foo(a2);
    __asm push sizeof(Foo);
    __asm call operator new;//there's a lot instruction here(depends on system)
    __asm push a2;
    __asm call Foo::Foo(int);

    delete f2;
}

यह asm में ऐसा होगा। जब आप कर रहे हैं func, f1और सूचक f2ढेर (स्वचालित भंडारण) पर आवंटित किया गया है। और वैसे, फू f1(a1)पॉइंटर पॉइंटर पर कोई निर्देश प्रभाव नहीं है ( esp), यह आवंटित किया गया है, यदि funcसदस्य चाहते हैं , तो f1यह निर्देश कुछ इस तरह है lea ecx [ebp+f1], call Foo::SomeFunc():। एक और बात यह है कि स्टैक आवंटित किया जा सकता है किसी को लगता है कि मेमोरी कुछ ऐसी है FIFO, FIFOजैसा कि आप किसी फंक्शन में जाने पर होता है, यदि आप फंक्शन में हैं और ऐसा कुछ आवंटित करते हैं int i = 0, तो कोई पुश नहीं हुआ।


1

यह पहले उल्लेख किया गया है कि स्टैक आवंटन बस स्टैक पॉइंटर को स्थानांतरित कर रहा है, अर्थात, अधिकांश आर्किटेक्चर पर एक एकल निर्देश। तुलना करें कि आम तौर पर ढेर आवंटन के मामले में क्या होता है।

ऑपरेटिंग सिस्टम निशुल्क मेमोरी के कुछ हिस्सों को पॉन्ड लोड डेटा के साथ एक लिंक्ड सूची के रूप में रखता है जिसमें पॉइंटर से मुक्त हिस्से के शुरुआती पते और मुक्त हिस्से का आकार होता है। एक्स बाइट्स ऑफ़ मेमोरी को आवंटित करने के लिए, लिंक लिस्ट को ट्रेस किया जाता है और प्रत्येक नोट को क्रम में देखा जाता है, यह देखने के लिए जाँचता है कि क्या इसका आकार कम से कम X है। जब आकार P> = X वाला भाग मिलता है, P को दो भागों में विभाजित किया जाता है आकार एक्स और पीएक्स। लिंक की गई सूची अपडेट की गई है और पहले भाग के लिए पॉइंटर लौटाया गया है।

जैसा कि आप देख सकते हैं, ढेर आवंटन कारकों पर निर्भर करता है जैसे कि आप कितनी मेमोरी का अनुरोध कर रहे हैं, मेमोरी कितनी खंडित है और इसी तरह।


1

सामान्य तौर पर, ढेर आवंटन, आवंटन आवंटन से तेज होता है, जैसा कि ऊपर दिए गए लगभग हर उत्तर द्वारा बताया गया है। स्टैक पुश या पॉप ओ (1) है, जबकि एक ढेर से आवंटित या मुक्त करने के लिए पिछले आवंटन की पैदल दूरी की आवश्यकता हो सकती है। हालांकि आपको आमतौर पर तंग, प्रदर्शन-गहन छोरों में आवंटित नहीं किया जाना चाहिए, इसलिए विकल्प आमतौर पर अन्य कारकों के लिए नीचे आ जाएगा।

यह भेद करना अच्छा हो सकता है: आप ढेर पर "स्टैक एलोकेटर" का उपयोग कर सकते हैं। सख्ती से बोलते हुए, मैं आवंटन के स्थान के बजाय आवंटन की वास्तविक विधि का मतलब करने के लिए स्टैक आवंटन लेता हूं। यदि आप वास्तविक कार्यक्रम स्टैक पर बहुत अधिक सामान आवंटित कर रहे हैं, तो यह कई कारणों से खराब हो सकता है। दूसरी ओर, ढेर पर आवंटित करने के लिए एक स्टैक विधि का उपयोग करना जब संभव हो तो सबसे अच्छा विकल्प है जो आप आवंटन विधि के लिए कर सकते हैं।

जब से आपने Metrowerks और PPC का उल्लेख किया है, मैं अनुमान लगा रहा हूं कि आप Wii हैं। इस मामले में मेमोरी एक प्रीमियम पर है, और जहां भी संभव हो, स्टैक आवंटन पद्धति का उपयोग करके यह गारंटी देता है कि आप टुकड़ों पर मेमोरी बर्बाद नहीं करते हैं। बेशक, ऐसा करने के लिए "सामान्य" ढेर आवंटन विधियों की तुलना में बहुत अधिक देखभाल की आवश्यकता होती है। प्रत्येक स्थिति के लिए ट्रेडऑफ का मूल्यांकन करना बुद्धिमानी है।


1

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

अब एक उदाहरण के लिए जहां स्टैक का उपयोग नहीं किया जा सकता है:

Proc P
{
  pointer x;
  Proc S
  {
    pointer y;
    y = allocate_some_data();
    x = y;
  }
}

यदि आप प्रक्रिया एस में कुछ मेमोरी आवंटित करते हैं और इसे स्टैक पर रख देते हैं और फिर एस से बाहर निकलते हैं, तो आवंटित डेटा स्टैक से पॉपअप हो जाएगा। लेकिन P में परिवर्तनशील x ने भी उस डेटा की ओर संकेत किया है, इसलिए x अब किसी अज्ञात सामग्री के साथ स्टैक पॉइंटर (मान लीजिए स्टैक नीचे की ओर बढ़ता है) के नीचे कुछ जगह की ओर इशारा कर रहा है। सामग्री तब भी हो सकती है यदि स्टैक पॉइंटर को केवल उसके नीचे के डेटा को साफ किए बिना ऊपर ले जाया जाता है, लेकिन यदि आप स्टैक पर नए डेटा को आवंटित करना शुरू करते हैं, तो पॉइंटर x वास्तव में उस नए डेटा को इंगित कर सकता है।


0

अन्य एप्लिकेशन कोड के रूप में कभी भी समय से पहले धारणा न करें और उपयोग आपके कार्य को प्रभावित कर सकता है। इसलिए फ़ंक्शन को देखने से अलगाव का कोई मतलब नहीं है।

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

केतन


-1

मैं वास्तव में जीसीसी द्वारा उत्पन्न कोड कहना चाहता हूं (मुझे याद है कि वीएस भी) ढेर आवंटन करने के लिए ओवरहेड नहीं है

निम्नलिखित कार्य के लिए कहें:

  int f(int i)
  {
      if (i > 0)
      {   
          int array[1000];
      }   
  }

निम्नलिखित कोड उत्पन्न है:

  __Z1fi:
  Leh_func_begin1:
      pushq   %rbp
  Ltmp0:
      movq    %rsp, %rbp
  Ltmp1:
      subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.
  Ltmp2:
      movl    %edi, -4(%rbp)
      movl    -8(%rbp), %eax
      addq    $3880, %rsp
      popq    %rbp
      ret 
  Leh_func_end1:

तो आपके पास कितना स्थानीय चर है (भले ही अंदर या अगर स्विच करें), बस 3880 दूसरे मूल्य में बदल जाएगा। जब तक आपके पास स्थानीय चर नहीं था, तब तक इस निर्देश को निष्पादित करने की आवश्यकता है। इसलिए स्थानीय चर आवंटित करें जिसके पास ओवरहेड नहीं है।

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