एक अच्छा सी चर लंबाई सरणी उदाहरण [बंद]


9

यह प्रश्न SO पर एक बहुत ही शानदार रिसेप्शन था, इसलिए मैंने इसे वहां हटाने और इसके बजाय यहाँ प्रयास करने का निर्णय लिया। यदि आपको लगता है कि यह यहां भी फिट नहीं है, तो कृपया कम से कम सुझाव पर एक टिप्पणी छोड़ें कि मैं किस तरह का उदाहरण पा सकता हूं ...

क्या आप एक उदाहरण दे सकते हैं , जहां C99 VLAs का उपयोग कर वर्तमान मानक हीप-सी + + RAII तंत्रों का उपयोग करने से अधिक लाभ मिलता है?

उदाहरण के बाद मैं कर रहा हूँ:

  1. ढेर का उपयोग करके आसानी से मापने योग्य (10% हो सकता है) प्रदर्शन लाभ प्राप्त करें।
  2. अच्छा वर्कअराउंड नहीं है, जिसे पूरे ऐरे की जरूरत नहीं होगी।
  3. वास्तव में निर्धारित अधिकतम आकार के बजाय गतिशील आकार का उपयोग करने से लाभ होता है।
  4. सामान्य उपयोग परिदृश्य में स्टैक ओवरफ्लो का कारण बनने की संभावना नहीं है।
  5. C ++ प्रोजेक्ट में C99 स्रोत फ़ाइल को शामिल करने के लिए प्रदर्शन की आवश्यकता वाले डेवलपर को लुभाने के लिए पर्याप्त मजबूत होना चाहिए।

संदर्भ पर कुछ स्पष्टीकरण जोड़ना: मेरा मतलब है कि सीएलए द्वारा वीएलए का मतलब है और मानक सी ++ में शामिल नहीं है: int array[n]जहां nएक चर है। और मैं उपयोग के मामले के उदाहरण के बाद हूं जहां यह अन्य मानकों (C90, C ++ 11) द्वारा प्रस्तुत विकल्पों को रौंदता है:

int array[MAXSIZE]; // C stack array with compile time constant size
int *array = calloc(n, sizeof int); // C heap array with manual free
int *array = new int[n]; // C++ heap array with manual delete
std::unique_ptr<int[]> array(new int[n]); // C++ heap array with RAII
std::vector<int> array(n); // STL container with preallocated size

कुछ विचार:

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

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

नोट: मैं विशेष रूप से एक उदाहरण कोड के बाद हूं, या एक एल्गोरिथ्म का सुझाव जो इस से लाभान्वित होगा, मेरे लिए उदाहरण स्वयं लागू करने के लिए।


1
थोड़ा सा सट्टा (चूंकि यह एक कील की तलाश में एक हथौड़ा है), लेकिन शायद बाद में ताला विवाद के कारण एक बहुआयामी वातावरण में alloca()वास्तव में बाहर निकल जाएगा । लेकिन यह एक वास्तविक खिंचाव है क्योंकि छोटे सरणियों को बस एक निश्चित आकार का उपयोग करना चाहिए, और बड़े सरणियों को संभवतः वैसे भी ढेर की आवश्यकता होगी। malloc()
चिरसायकॉक

1
@chrisaycock हां, बहुत ज्यादा एक नाखून की तलाश में हथौड़ा है, लेकिन एक हथौड़ा जो वास्तव में मौजूद है (यह C99 VLA या नहीं-वास्तव में-किसी भी मानक है alloca, जो मुझे लगता है कि मूल रूप से एक ही बात है)। लेकिन यह बहुस्तरीय चीज़ अच्छी है, इसे शामिल करने के लिए संपादन प्रश्न!
हाइड

वीएलएएस का एक नुकसान यह है कि आवंटन विफलता का पता लगाने के लिए कोई तंत्र नहीं है; यदि पर्याप्त स्मृति नहीं है, तो व्यवहार अपरिभाषित है। (समान निश्चित-आकार सरणियों के लिए सच है -। और alloca () के लिए)
कीथ थॉम्पसन

@KeithThompson वैसे, इस बात की कोई गारंटी नहीं है कि मॉलॉक / नई आवंटन विफलता का पता लगाता है, उदाहरण के लिए लिनक्स मॉलॉक मैन पेज ( linux.die.net/man/3/malloc ) के लिए नोट्स देखें ।
हाईड

@ प्रेस: ​​और यह बहस का विषय है कि क्या लिनक्स का mallocव्यवहार सी मानक के अनुरूप है।
कीथ थॉम्पसन

जवाबों:


9

मैंने बस एक छोटा प्रोग्राम हैक किया है जो हर बार एक ही बीज पर पुनः शुरू होने वाले यादृच्छिक संख्याओं का एक सेट उत्पन्न करता है, यह सुनिश्चित करने के लिए कि यह "उचित" और "तुलनीय" है। जैसे-जैसे यह आगे बढ़ता है, यह इन मूल्यों के न्यूनतम और अधिकतम का पता लगाता है। और जब इसने संख्याओं का समुच्चय उत्पन्न किया है, तो यह मायने रखता है कि कितने औसत minऔर से अधिक हैं max

बहुत छोटे सरणियों के लिए, यह वीएलए के ओवर के साथ एक स्पष्ट लाभ दिखाता है std::vector<>

यह एक वास्तविक समस्या नहीं है, लेकिन हम आसानी से किसी चीज़ की कल्पना कर सकते हैं, जहाँ हम रैंडम नंबरों का उपयोग करने के बजाय एक छोटी फ़ाइल से मान पढ़ रहे होंगे, और उसी तरह के कोड के साथ कुछ अन्य, अधिक सार्थक गिनती / मिनट / अधिकतम गणनाएँ करेंगे। ।

संबंधित कार्यों में "यादृच्छिक संख्याओं की संख्या" (x) के छोटे मानों के लिए, vlaसमाधान एक बड़े अंतर से जीतता है। जैसा कि आकार बड़ा होता है, "जीत" छोटा हो जाता है, और पर्याप्त आकार दिया जाता है, वेक्टर समाधान अधिक कुशल प्रतीत होता है - उस संस्करण का बहुत अधिक अध्ययन नहीं किया, जैसे कि जब हम एक वीएलए में हजारों तत्वों को रखना शुरू करते हैं, तो यह नहीं है वास्तव में वे क्या करने के लिए थे ...

और मुझे यकीन है कि कोई मुझे बताएगा कि टेम्प्लेट के एक समूह के साथ यह सब कोड लिखने का कुछ तरीका है और इसे आरडीटीसीटी और coutबिट्स से अधिक रनटाइम के बिना ऐसा करने के लिए मिलता है ... लेकिन मुझे नहीं लगता कि यह वास्तव में है बिंदु।

इस विशेष संस्करण को चलाते समय, मुझे func1(VLA) और func2(std :: वेक्टर) के बीच लगभग 10% अंतर मिलता है ।

count = 9884
func1 time in clocks per iteration 7048685
count = 9884
func2 time in clocks per iteration 7661067
count = 9884
func3 time in clocks per iteration 8971878

इसके साथ संकलित किया गया है: g++ -O3 -Wall -Wextra -std=gnu++0x -o vla vla.cpp

यहाँ कोड है:

#include <iostream>
#include <vector>
#include <cstdint>
#include <cstdlib>

using namespace std;

const int SIZE = 1000000;

uint64_t g_val[SIZE];


static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}


int func1(int x)
{
    int v[x];

    int vmax = 0;
    int vmin = x;
    for(int i = 0; i < x; i++)
    {
        v[i] = rand() % x;
        if (v[i] > vmax) 
            vmax = v[i];
        if (v[i] < vmin) 
            vmin = v[i];
    }
    int avg = (vmax + vmin) / 2;
    int count = 0;
    for(int i = 0; i < x; i++)
    {
        if (v[i] > avg)
        {
            count++;
        }
    }
    return count;
}

int func2(int x)
{
    vector<int> v;
    v.resize(x); 

    int vmax = 0;
    int vmin = x;
    for(int i = 0; i < x; i++)
    {
        v[i] = rand() % x;
        if (v[i] > vmax) 
            vmax = v[i];
        if (v[i] < vmin) 
            vmin = v[i];
    }
    int avg = (vmax + vmin) / 2;
    int count = 0;
    for(int i = 0; i < x; i++)
    {
        if (v[i] > avg)
        {
            count++;
        }
    }
    return count;
}    

int func3(int x)
{
    vector<int> v;

    int vmax = 0;
    int vmin = x;
    for(int i = 0; i < x; i++)
    {
        v.push_back(rand() % x);
        if (v[i] > vmax) 
            vmax = v[i];
        if (v[i] < vmin) 
            vmin = v[i];
    }
    int avg = (vmax + vmin) / 2;
    int count = 0;
    for(int i = 0; i < x; i++)
    {
        if (v[i] > avg)
        {
            count++;
        }
    }
    return count;
}    

void runbench(int (*f)(int), const char *name)
{
    srand(41711211);
    uint64_t long t = rdtsc();
    int count = 0;
    for(int i = 20; i < 200; i++)
    {
        count += f(i);
    }
    t = rdtsc() - t;
    cout << "count = " << count << endl;
    cout << name << " time in clocks per iteration " << dec << t << endl;
}

struct function
{
    int (*func)(int);
    const char *name;
};


#define FUNC(f) { f, #f }

function funcs[] = 
{
    FUNC(func1),
    FUNC(func2),
    FUNC(func3),
}; 


int main()
{
    for(size_t i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++)
    {
        runbench(funcs[i].func, funcs[i].name);
    }
}

वाह, मेरे सिस्टम ने VLA संस्करण में 30% सुधार दिखाया है std::vector
क्रिस

1
खैर, 20-200 के बजाय लगभग 5-15 के आकार-सीमा के साथ प्रयास करें, और आपके पास संभवतः 1000% या अधिक सुधार होगा। [इसके अलावा संकलक विकल्पों पर निर्भर करता है - मैं अपने संकलक विकल्पों को gcc पर दिखाने के लिए उपरोक्त कोड संपादित करूंगा]
मैट पीटर्सन

मैंने सिर्फ एक जोड़ा है func3जो v.push_back(rand())इसके बजाय का उपयोग करता है v[i] = rand();और इसकी आवश्यकता को हटा देता है resize()। इसका उपयोग करने वाले की तुलना में लगभग 10% अधिक समय लगता है resize()। [निश्चित रूप से, इस प्रक्रिया में, मैंने पाया कि v[i]जिस कार्य में समय लगता है - उसमें एक प्रमुख योगदानकर्ता का उपयोग होता है - मैं इससे थोड़ा आश्चर्यचकित हूं]।
मैट पीटरसन

1
@ माइकबर्न क्या आपको एक वास्तविक std::vectorकार्यान्वयन के बारे में पता है जो VLA / का उपयोग करेगा alloca, या यह केवल अटकलें हैं?
हाइड

3
वेक्टर वास्तव में आंतरिक रूप से एक सरणी का उपयोग करता है, लेकिन जहां तक ​​मैं समझता हूं, इसका वीएलए का उपयोग करने का कोई तरीका नहीं है। मेरा मानना ​​है कि मेरे उदाहरण से पता चलता है कि वीएलए कुछ (शायद कई भी) मामलों में उपयोगी है जहां डेटा की मात्रा छोटी है। यहां तक ​​कि अगर वेक्टर वीएलए का है, तो यह vectorकार्यान्वयन के अंदर अतिरिक्त प्रयास के बाद होगा ।
मैट पीटरसन

0

वीएलएएस बनाम एक वेक्टर के बारे में

क्या आपने विचार किया कि एक वेक्टर खुद वीएलए का लाभ ले सकता है। वीएलए के बिना, वेक्टर को स्टोरेज के लिए 10, 100, 10000 के एरेज़ के कुछ "स्केल" निर्दिष्ट करने होंगे ताकि आप 101 आइटम रखने के लिए 10000 आइटम सरणी आवंटित कर सकें। वीएलएएस के साथ, यदि आप 200 का आकार बदलते हैं, तो एल्गोरिथ्म मान सकता है कि आपको केवल 200 की आवश्यकता होगी और 200 आइटम सरणी आवंटित कर सकता है। या यह कहे * n 1.5 का एक बफर आवंटित कर सकता है।

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

वेक्टर से उसके आंतरिक सरणी में अतिरिक्त पॉइंटर जम्प जुड़ जाता है।

मुख्य प्रश्न का उत्तर दे रहे हैं

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

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


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

std::vectorसरणियों की आवश्यकता क्यों होगी ? इसे 10K तत्वों के लिए स्थान की आवश्यकता क्यों होगी जब इसे केवल 101 की आवश्यकता होगी? इसके अलावा, प्रश्न में लिंक की गई सूचियों का उल्लेख नहीं है, इसलिए मुझे यकीन नहीं है कि आपको वह कहाँ से मिला है। अंत में, C99 में VLAs स्टैक-आबंटित हैं; वे एक मानक रूप हैं alloca()। किसी भी चीज को ढेर भंडारण की आवश्यकता होती है (यह फ़ंक्शन रिटर्न के बाद चारों ओर रहता है) या realloc()( ए सरणी खुद को आकार देता है) वैसे भी वीएलए को प्रतिबंधित करेगा।
क्रिस

@chrisaycock C ++ में किसी कारण से वास्तविक () फ़ंक्शन का अभाव है, यह मानते हुए कि मेमोरी को नए [] के साथ आवंटित किया गया है। क्या यह मुख्य कारण नहीं है कि एसटीडी :: वेक्टर को तराजू का उपयोग करना चाहिए?

@ लुंडिन क्या सी ++ दस की शक्तियों द्वारा वेक्टर को मापता है? मुझे बस यह आभास हो गया था कि माइक ब्राउन वास्तव में इस प्रश्न से भ्रमित थे, जो कि सूची से जुड़ा हुआ है। (उन्होंने यह भी दावा किया कि C99 VLAs ने ढेर पर रहते हुए पहले दावा किया था।)
chrisaycock

@ मुझे एहसास नहीं हुआ कि आप किस बारे में बात कर रहे हैं। मैंने सोचा था कि आप अन्य ढेर आधारित डेटा संरचनाओं का मतलब है। अब दिलचस्प है कि आपने यह स्पष्टीकरण जोड़ दिया है। मैं आपको उन लोगों के बीच अंतर बताने के लिए C ++ geek के लिए पर्याप्त नहीं हूं।
माइकल ब्राउन

0

वीएलए का उपयोग करने का कारण मुख्य रूप से प्रदर्शन है। यह केवल एक "अप्रासंगिक" अंतर होने के रूप में विकि उदाहरण की अवहेलना करने के लिए एक गलती है। मैं उन मामलों को आसानी से देख सकता हूं, जहां वास्तव में उस कोड में बहुत बड़ा अंतर हो सकता है, उदाहरण के लिए, यदि उस फ़ंक्शन को एक तंग लूप में बुलाया गया था, जहां read_valएक IO फ़ंक्शन था जो कुछ प्रकार की प्रणाली पर बहुत तेज़ी से लौटा था जहां गति महत्वपूर्ण थी।

वास्तव में, अधिकांश स्थानों पर जहां वीएलएएस इस तरीके से उपयोग किए जाते हैं, वे ढेर कॉलों को प्रतिस्थापित नहीं करते हैं, बल्कि कुछ इस तरह से प्रतिस्थापित करते हैं:

float vals[256]; /* I hope we never get more! */

किसी भी स्थानीय घोषणा के बारे में बात यह है कि यह बहुत जल्दी है। float vals[n]आम तौर पर लाइन को केवल प्रोसेसर निर्देशों के एक जोड़े की आवश्यकता होती है (शायद सिर्फ एक।) यह केवल nस्टैक पॉइंटर में मूल्य जोड़ता है ।

दूसरी ओर, एक ढेर आवंटन को एक मुक्त क्षेत्र खोजने के लिए डेटा संरचना चलने की आवश्यकता होती है। समय शायद सौभाग्य के मामले में भी लंबे समय तक परिमाण का एक क्रम है। (यानी nस्टैक पर कॉल करने की क्रिया और कॉलिंग mallocशायद 5-10 निर्देश है।) यदि ढेर में कोई उचित मात्रा में डेटा है, तो संभवतः बहुत बुरा। यह मुझे बिल्कुल भी आश्चर्यचकित नहीं करेगा कि mallocएक वास्तविक कार्यक्रम में 100x से 1000x धीमा करने वाले मामले को कैसे देखा जाए ।

बेशक, फिर आपके पास मिलान के साथ कुछ प्रदर्शन प्रभाव भी हैं free, शायद mallocकॉल के परिमाण के समान ।

इसके अलावा, स्मृति विखंडन का मुद्दा है। बहुत कम आवंटन से ढेर के टुकड़े होते हैं। खंडित दोनों बेकार स्मृति को ढेर कर देता है और स्मृति को आवंटित करने के लिए आवश्यक समय बढ़ाता है।


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

उम ... यकीन नहीं है कि तुम क्या मतलब है। स्थानीय परिवर्तनीय घोषणाएं एक एकल ऑपरेशन हैं और किसी भी हल्के से अनुकूलित कंपाइलर आवंटन को एक आंतरिक लूप से बाहर खींच लेंगे। स्थानीय चर तक पहुँचने में कोई विशेष "लागत" नहीं है, निश्चित रूप से ऐसा नहीं है कि वीएलए बढ़ेगा।
रोबोट

ठोस उदाहरण:: int vla[n]; if(test()) { struct LargeStruct s; int i; }स्टैक ऑफ़सेट का sसंकलन समय पर ज्ञात नहीं होगा, और यह भी संदेहास्पद है कि कंपाइलर iआंतरिक दायरे से बाहर भंडारण को स्टैक ऑफ़सेट में ले जाएगा। इसलिए अतिरिक्त मशीन कोड की आवश्यकता है क्योंकि अप्रत्यक्ष, और यह पीसी हार्डवेयर पर महत्वपूर्ण रजिस्टर भी खा सकता है। ); आप संकलक विधानसभा उत्पादन शामिल उदाहरण कोड चाहते हैं, एक अलग सवाल पूछते हैं तो कृपया
हाइड

इस संकलक को कोड में दिए गए क्रम में आवंटित नहीं करना पड़ता है, और इससे कोई फर्क नहीं पड़ता कि अंतरिक्ष आवंटित किया गया है और इसका उपयोग नहीं किया गया है। एक स्मार्ट ऑप्टिमाइज़र के लिए जगह आवंटित करेगा sऔर iजब फ़ंक्शन में प्रवेश किया testजाता है , तो पहले बुलाया जाता है या vlaआवंटित किया जाता है, क्योंकि इसके साइड इफेक्ट्स होते हैं sऔर iउनके दुष्प्रभाव होते हैं। (और, वास्तव में, iएक रजिस्टर में भी रखा जा सकता है, जिसका अर्थ है कि कोई "आवंटन" बिल्कुल नहीं है।) स्टैक पर आवंटन के आदेश के लिए कोई कंपाइलर गारंटी नहीं है, या यहां तक ​​कि स्टैक का उपयोग किया जाता है।
रोबोट

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