2009-03-03 15 views
12

पिछले हफ्ते मैंने एक बड़ी टेक्स्ट फ़ाइल (300,000 लाइन) को एक शब्दकोश में आग लगाने के लिए सी # में कोड की कुछ पंक्तियां लिखीं। इसे लिखने में दस मिनट लग गए और इसे एक सेकंड से भी कम समय में निष्पादित किया गया।सी ++ स्ट्रिंग मेमोरी प्रबंधन

अब मैं कोड के उस टुकड़े को C++ में परिवर्तित कर रहा हूं (क्योंकि मुझे इसे पुराने सी ++ COM ऑब्जेक्ट में चाहिए)। मैंने इस पर दो दिन बिताए हैं। :-(हालांकि उत्पादकता अंतर अपने आप पर चौंकाने वाला है, यह प्रदर्शन है कि मुझे कुछ सलाह चाहिए।

लोड करने में सात सेकंड लगते हैं, और इससे भी बदतर: यह बिल्कुल ठीक है कि सभी को मुक्त करने में काफी समय लगता है CStringWs बाद में। यह स्वीकार्य नहीं है, और मैं प्रदर्शन को बढ़ाने के लिए एक रास्ता खोजने होंगे।

वहाँ कोई मौका है कि मैं इस भयानक performace गिरावट देखे बिना इस कई तार आवंटित कर सकते हैं?

मेरा अनुमान है अभी कि मुझे सभी टेक्स्ट को एक बड़े सरणी में भरना होगा और फिर मेरी हैश तालिका इस सरणी के भीतर प्रत्येक स्ट्रिंग की शुरुआत को इंगित करेगी और CStringW सामग्री को छोड़ दें।

लेकिन इससे पहले, सी ++ विशेषज्ञों से आपसे कोई सलाह क्या है?

संपादित करें: मेरा उत्तर नीचे दिया गया है। मुझे एहसास हुआ कि यह मेरे लिए सबसे तेज़ मार्ग है, और I पर भी कदम उठाएं सही दिशा पर विचार करें - अधिक प्रबंधित कोड की ओर।

+0

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

+0

आपको निश्चित रूप से अपनी डेटा संरचना के बारे में अधिक जानकारी प्रदान करने की आवश्यकता है ... –

+1

इससे पहले कि मैं .NET से परिचित था, सी ++ से सी # में संक्रमण - बिना किसी विचार के कि फ्रेमवर्क (मेरे मामले में वीसीएल) समान या अलग थे - क्या होगा दूसरी तरफ के रूप में चुनौतीपूर्ण रहा है। अपने प्रश्न पूछने के लिए सी ++ को दोष देने की आवश्यकता नहीं है। – overslacked

उत्तर

11

आप रेमंड चेन के जूते में कदम रख रहे हैं। उन्होंने एक ही चीज़ की, एक अप्रचलित सी ++ में एक चीनी शब्दकोश लिखना। रिको मारियानी ने भी सी # में लिखा था। श्री मारियानी ने एक संस्करण बनाया। श्री चेन ने मारियानी के संस्करण के पेर्फ से मिलान करने की कोशिश कर 6 संस्करणों को लिखा। वह वहां पहुंचने के लिए सी/सी ++ रनटाइम लाइब्रेरी के महत्वपूर्ण हिस्सों को काफी हद तक पुनः लिखता है।

प्रबंधित कोड उसके बाद बहुत अधिक सम्मान प्राप्त हुआ। जीसी आवंटन को हरा करना असंभव है। लिंक के लिए this blog पोस्ट देखें। यह blog post आपको भी रूचि दे सकता है, यह देखने के लिए निर्देशक कि एसटीएल मूल्य अर्थशास्त्र समस्या का हिस्सा हैं।

+0

ठीक है, मारियानी को उद्धृत करने के लिए "तो, हाँ, आप निश्चित रूप से सीएलआर को हरा सकते हैं।" लेकिन मैं सहमत हूं कि सीएलआर प्रदर्शन बहुत प्रभावशाली है। –

3

आप किस प्रकार के कंटेनर को अपने तारों को संग्रहित कर रहे हैं? यदि यह CStringW है और यदि आपके पास reserve पहले से पर्याप्त स्मृति नहीं है, तो आप एक हिट लेने के लिए बाध्य हैं। एक vector आम तौर पर इसकी सीमा तक पहुंचने के बाद आकार बदलता है (जो बहुत अधिक नहीं है) और फिर पूरी तरह से नई स्मृति स्थान पर प्रतिलिपि बनाता है जो आपको एक बड़ी हिट दे सकता है। चूंकि आपका vector तेजी से बढ़ता है (यानी यदि प्रारंभिक आकार 1 है, अगली बार जब यह 2, 4 अगली बार आवंटित करता है, तो हिट कम और कम हो जाती है)।

यह यह जानने में भी मदद करता है कि व्यक्तिगत तार कितने समय तक हैं। (कई बार :)

+0

फिक्स्ड सरणी ...क्या आप सभी नई स्ट्रिंग के साथ सरणी को ऊपर या आकार देने के लिए सभी मेमोरी आवंटित कर रहे हैं? –

+0

नहीं, नहीं, नहीं। मैं इसे कभी साकार नहीं करता हूं। यह एक हैश टेबल है और * दुर्लभ * अवसरों में जहां मुझे डुप्लिकेट हैश कुंजी मिलती है, कुछ अतिरिक्त चीजें होती हैं, लेकिन मेरे बेंचमार्किंग के दौरान मैं वास्तव में यह सुनिश्चित करने के लिए डुप्लिकेट फेंक देता हूं कि यह कोई प्रदर्शन समस्या नहीं है। –

+0

ऐसा लगता है कि आपके पास सब कुछ पूरी तरह से स्थापित है। क्या आपने std :: wstring के साथ प्रयास किया था? साथ ही, यह एक प्रोफाइलर की मदद लेने के बारे में समय है। – dirkgently

10

यिक्स। CStrings से छुटकारा पाएं ...

एक प्रोफाइलर को भी आजमाएं। क्या आप वाकई डीबग कोड नहीं चला रहे हैं?

इसके बजाय std :: string का उपयोग करें।

संपादित करें:

मैं सिर्फ ctor और dtor तुलना की एक साधारण परीक्षण किया था।

CStringW को नया/हटाने के लिए 2 और 3 बार समय लगता है।

प्रत्येक प्रकार के लिए 1000000 बार नए/हटाए गए। कुछ और नहीं - और प्रत्येक लूप से पहले और बाद में GetTickCount() कॉल करें। CStringW के लिए लगातार दो बार मिलता है।

हालांकि मुझे संदेह है कि यह आपके पूरे मुद्दे को संबोधित नहीं करता है।

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

(लेकिन भगवान की खातिर, एसटीएल वैसे भी उपयोग करें!)

आप इसे प्रोफ़ाइल की जरूरत है। यह एक आपदा है।

+0

मैं std :: स्ट्रिंग आज़माउंगा, ट्यूनेड रहें! :-) –

+0

सीएसटींग वह बुरा नहीं है ... – peterchen

+1

सीएसटींग खराब है। सबसे पहले, जैसा कि मैंने अभी पुष्टि की है, यह std :: स्ट्रिंग से धीमी है। यह पोर्टेबल नहीं है। यह एक एमएफसी हैक - अन्य सभी एमएफसी जंक के साथ। मुझे गलत मत समझो, मैंने वर्षों से उनका उपयोग किया है, लेकिन कम एमएफसी हम उपयोग करते हैं, बेहतर ... – Tim

1

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

4

यदि यह केवल पढ़ने के लिए शब्दकोश है तो निम्नलिखित आपके लिए काम करना चाहिए।

Use fseek/ftell functionality, to find the size of the text file. 

Allocate a chunk of memory of that size + 1 to hold it. 

fread the entire text file, into your memory chunk. 

Iterate though the chunk. 

    push_back into a vector<const char *> the starting address of each line. 

    search for the line terminator using strchr. 

    when you find it, deposit a NUL, which turns it into a string. 
    the next character is the start of the next line 

until you do not find a line terminator. 

Insert a final NUL character. 

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

जब आप अपने शब्दकोश के साथ समाप्त हो जाते हैं, तो स्मृति को हटा दें, वेक्टर गुंजाइश से बाहर निकलने दें।

[संपादित करें] यह डॉस प्लेटफ़ॉर्म पर थोड़ा अधिक जटिल हो सकता है, क्योंकि लाइन टर्मिनेटर सीआरएलएफ है।

उस स्थिति में, इसे खोजने के लिए स्ट्रस्ट्र का उपयोग करें, और अगली पंक्ति की शुरुआत को खोजने के लिए 2 तक वृद्धि करें।

+0

यह बहुत अच्छा काम करेगा और तेज़ होगा। मुझे नहीं लगता कि यह जरूरी है - वह अपने कोड में कुछ बुरा कर रहा है जिसे शायद तय किया जा सकता है और पठनीय और सरल कोड हो सकता है। – Tim

+0

जो चीज इसे मार रही है वह स्मृति आवंटन/विध्वंस के साथ निर्माण/विनाश लागत है। – EvilTeach

+0

या (यदि Win32 समकक्ष का समर्थन करता है) फ़ाइल MAP_PRIVATE (लिखने पर प्रतिलिपि) को mmap करें, जो इसे कॉपी करने से बचाता है। –

12

यह बहुत ज्यादा रेमंड चेन बनाम रीको मरिअनी के सी ++ बनाम सी # चीनी/अंग्रेज़ी शब्दकोश प्रदर्शन की तरह से बेक लग रहा है। सी # को हरा करने के लिए रेमंड ने कई पुनरावृत्तियों को लिया।

शायद ऐसे विचार हैं जो मदद करेंगे।

http://blogs.msdn.com/ricom/archive/2005/05/10/performance-quiz-6-chinese-english-dictionary-reader.aspx

+0

+1 हाँ, आप नहीं जानते कि आप कितने सही हैं! :-) लेकिन मेरे लिए एक अतिरिक्त मुश्किल बात यह है कि मैं एक बार में एएससीआईआईआई, यूटीएफ 8 और यूनिकोड को पढ़ना चाहता हूं, इसलिए मुझे फ़ाइल डेटा से डेटा को स्टोर करने के लिए एक अनुवाद करना होगा। – EvilTeach

+0

हाँ के लिए –

+1

@ डैनबीस्ट्रॉम, एएससीआईआईआई को यूटीएफ -8 का सबसेट माना जा सकता है। यूटीएफ -8 एक यूनिकोड एन्कोडिंग प्रारूप है। यदि आपके पास यूटीएफ -8 में आपकी फाइलें हैं तो आप यूनिकोड में परिभाषित किसी भी वर्ण का उपयोग कर सकते हैं और मौजूदा ASCII पाठ को पुनः-एन्कोडिंग की आवश्यकता नहीं है। – CMircea

2

लोड एक भी बफर करने के लिए स्ट्रिंग, पाठ लाइन स्ट्रिंग टर्मिनेटर्स साथ टूट जाता है को बदलने के लिए पार्स ('\ 0'), और कहा कि बफर में उपयोग संकेत दिए गए सेट में जोड़ने के लिए।

वैकल्पिक रूप से - जैसे यदि आपको भार के दौरान एएनएसआई/यूनिकोड रूपांतरण करना है - एक खंड आवंटक का उपयोग करें, जो अलग-अलग तत्वों को हटाने का बलिदान करता है।

class ChunkAlloc 
{ 
    std::vector<BYTE> m_data; 
    size_t m_fill; 
    public: 
    ChunkAlloc(size_t chunkSize) : m_data(size), m_fill(0) {} 
    void * Alloc(size_t size) 
    { 
     if (m_data.size() - m_fill < size) 
     { 
      // normally, you'd reserve a new chunk here 
      return 0; 
     } 
     void * result = &(m_data[m_fill]); 
     m_fill += size; 
     return m_fill; 
    } 
} 
// all allocations from chuunk are freed when chung is destroyed. 

एक साथ कि हैक चाहेंगे नहीं दस मिनट में, लेकिन 30 मिनट और कुछ परीक्षण लगता है अपने व्यावहारिक टिप्पणियों के लिए आप सभी को ठीक :)

+0

हां हां हां। रात के दौरान भी यही पता चला है! :-) –

+0

:) देखें रेमंड बनाम रिको (http://blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx) स्पष्ट रूप से आपके प्रोजेक्ट के समान है: सी ++ के साथ आप कर सकते हैं सी # ऐप की तुलना में आपकी समस्या को तेज़ी से हल करें, लेकिन यह आपको खर्च करता है। बहुत। – peterchen

3

धन्यवाद। आपके लिए उपरोक्त! :-)

मैं मानता चाहिए मैं इस के लिए तैयार नहीं था - कि सी # इस तरह से अच्छे पुराने सी ++ से बाहर रहने वाले बकवास हरा होता। कृपया सी ++ के लिए एक अपमान के रूप में है कि पढ़ने के लिए नहीं है, लेकिन बजाय क्या एक आश्चर्यजनक अच्छा स्मृति प्रबंधक कि .नेट फ्रेमवर्क के अंदर बैठता है।

मैंने एक कदम वापस लेने का फैसला किया और इसके बजाय इंटरऑप क्षेत्र में इस लड़ाई से लड़ने का फैसला किया! यही है, मैं अपना सी # कोड रखूंगा और अपने पुराने सी ++ कोड को COM इंटरफ़ेस पर सी # कोड से बात करूंगा।

बहुत सारे प्रश्न मेरे कोड के बारे में पूछा गया था और मैं उनमें से कुछ जवाब देने की कोशिश करेंगे:

  • संकलक विजुअल स्टूडियो 2008 था और नहीं, मैं एक डीबग बिल्ड नहीं चल रहा था।

  • फ़ाइल को एक यूटीएफ 8 फ़ाइल रीडर के साथ पढ़ा गया था जिसे मैंने माइक्रोसॉफ्ट कर्मचारी से डाउनलोड किया था, जिसने इसे अपनी साइट पर प्रकाशित किया था। यह CStringW लौटा और लगभग 30% वास्तव में फाइल पढ़ने के लिए वास्तव में खर्च किया गया था।

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

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

+0

सारांश के लिए धन्यवाद। – Tim

3

समस्या CString में नहीं है, बल्कि कि आप बहुत सी छोटी वस्तुओं को आवंटित कर रहे हैं - इसके लिए डिफ़ॉल्ट मेमोरी आवंटक अनुकूलित नहीं है।

अपना खुद का आवंटक लिखें - स्मृति का एक बड़ा हिस्सा आवंटित करें और फिर आवंटित करते समय इसमें एक पॉइंटर अग्रिम करें। यह वास्तव में .NET आवंटक करता है। जब आप तैयार हों तो पूरे बफर को हटा दें।

मुझे लगता है कि कस्टम नया लिखने की नमूना था/(अधिक) प्रभावी सी ++ में ऑपरेटरों

+0

हालांकि मैंने कभी भी इसका उपयोग करने की कोशिश नहीं की, मानक सी ++ लाइब्रेरी के कंटेनर एक ऑलोकेटर को पैरामीटर के रूप में स्वीकार करते हैं, ताकि आइटम निर्माण को ट्यून/ऑप्टिमाइज़ किया जा सके। जरूरत है अगर। –

0

यह कोई आश्चर्य नहीं है कि CLR के स्मृति प्रबंधन पुराने और गंदे चाल MFC पर आधारित है के गुच्छा की तुलना में बेहतर है हटा दें: यह है एमएफसी की तुलना में कम से कम दो गुना कम, और यह पूल आधारित है। जब मुझे स्ट्रिंग एरे और विनैपी/एमएफसी के साथ समान प्रोजेक्ट पर काम करना पड़ा, तो मैंने केवल WinDI के TCHAR के साथ std :: basic_string और लोकी :: SmallObjAllocator के आधार पर अपना स्वयं का आवंटन किया। आप इस मामले में boost :: पूल पर भी एक नज़र डाल सकते हैं (यदि आप चाहते हैं कि यह "std feel" हो या 7.1 से पुराने वीसी ++ कंपाइलर के संस्करण का उपयोग करना पड़े)।

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