C ++ में कुशल स्ट्रिंग समवर्ती


108

मैंने सुना है कि कुछ लोग std :: string और विभिन्न workarounds में "+" ऑपरेटर के बारे में चिंता व्यक्त करते हैं ताकि गति बढ़े। क्या इनमें से कोई भी वास्तव में आवश्यक है? यदि हां, तो C ++ में तारों को समतल करने का सबसे अच्छा तरीका क्या है?


13
मूल रूप से + समवर्ती संचालक नहीं है (क्योंकि यह एक नया तार उत्पन्न करता है)। संघनन के लिए + = का उपयोग करें।
मार्टिन यॉर्क

1
C ++ 11 के बाद से, एक महत्वपूर्ण बिंदु है: ऑपरेटर + अपने एक ऑपरेंड को संशोधित कर सकता है और इसे वापस ले जा सकता है अगर उस ऑपरेंड को rvalue संदर्भ द्वारा पारित किया गया था। libstdc++ उदाहरण के लिए, यह करता है । इसलिए, जब आपरेटरों + को टेम्पोररी के साथ बुलाते हैं, तो यह लगभग-जैसा-अच्छा प्रदर्शन प्राप्त कर सकता है - शायद इसे पठनीयता के पक्ष में एक तर्क, पठनीयता के लिए, जब तक कि कोई बेंचमार्क नहीं दिखा रहा है कि यह एक अड़चन है। हालांकि, एक मानकीकृत वैरेडिक append()इष्टतम और पठनीय दोनों होगा ...
अंडरस्कोर_ड

जवाबों:


85

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

अब उस अस्वीकरण के बाद, मैं आपके वास्तविक प्रश्न का उत्तर दूंगा ...

एसटीएल स्ट्रिंग क्लास की दक्षता आपके द्वारा उपयोग किए जा रहे एसटीएल के कार्यान्वयन पर निर्भर करती है।

आप कार्यकुशलता की गारंटी दे सकते हैं और सी-बिल्ट फंक्शंस के माध्यम से मैन्युअल रूप से कंसंट्रेशन करके खुद पर अधिक नियंत्रण रख सकते हैं।

ऑपरेटर + कुशल क्यों नहीं है:

इस इंटरफ़ेस पर एक नज़र डालें:

template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
          const basic_string<charT, traits, Alloc>& s2)

आप देख सकते हैं कि प्रत्येक के बाद एक नया ऑब्जेक्ट लौटाया जाता है। इसका मतलब है कि हर बार एक नए बफर का उपयोग किया जाता है। यदि आप एक टन अतिरिक्त + संचालन कर रहे हैं तो यह कुशल नहीं है।

आप इसे और अधिक कुशल क्यों बना सकते हैं:

  • आप एक प्रतिनिधि को अपने लिए कुशलतापूर्वक करने के लिए भरोसा करने के बजाय दक्षता की गारंटी दे रहे हैं
  • std :: string class आपके स्ट्रिंग के अधिकतम आकार के बारे में कुछ नहीं जानता है, और न ही कितनी बार आप इसे संक्षिप्त करेंगे। आपके पास यह ज्ञान हो सकता है और इस जानकारी के आधार पर चीजें कर सकते हैं। इससे पुन: आवंटन कम होंगे।
  • आप मैन्युअल रूप से बफ़र्स को नियंत्रित कर रहे होंगे ताकि आप सुनिश्चित हो सकें कि जब आप ऐसा नहीं करना चाहते हैं तो आप पूरी स्ट्रिंग को नए बफ़र्स में कॉपी नहीं करेंगे।
  • आप ढेर के बजाय अपने बफ़र्स के लिए स्टैक का उपयोग कर सकते हैं जो बहुत अधिक कुशल है।
  • string + ऑपरेटर एक नया स्ट्रिंग ऑब्जेक्ट बनाएगा और इसे वापस लौटाएगा इसलिए नए बफर का उपयोग किया जाएगा।

कार्यान्वयन के लिए विचार:

  • स्ट्रिंग की लंबाई का ध्यान रखें।
  • स्ट्रिंग और प्रारंभ के अंत में एक सूचक रखें, या बस प्रारंभ करें और स्ट्रिंग के अंत को खोजने के लिए एक ऑफसेट के रूप में प्रारंभ + लंबाई का उपयोग करें।
  • सुनिश्चित करें कि आप जिस बफर में अपना स्ट्रिंग स्टोर कर रहे हैं, वह इतना बड़ा है कि आपको डेटा को फिर से आवंटित करने की आवश्यकता नहीं है
  • स्ट्रैच के बजाय स्ट्रैची का उपयोग करें ताकि आपको स्ट्रिंग के अंत को खोजने के लिए स्ट्रिंग की लंबाई पर पुनरावृति करने की आवश्यकता न हो।

रस्सी डेटा संरचना:

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


6
नोट: "एसटीएल" एक पूरी तरह से अलग-ओपन ओपन-सोर्स लाइब्रेरी को संदर्भित करता है, मूल रूप से एचपी द्वारा, जिनमें से कुछ भाग को आईएसओ मानक सी ++ लाइब्रेरी के कुछ हिस्सों के आधार के रूप में उपयोग किया गया था। "std :: string", हालाँकि, कभी भी HP के STL का हिस्सा नहीं थी, इसलिए "STL और स्ट्रिंग" को एक साथ संदर्भित करना पूरी तरह से गलत है।
James Curran

1
मैं यह नहीं कहूंगा कि एसटीएल और स्ट्रिंग का एक साथ उपयोग करना गलत है। देखें sgi.com/tech/stl/table_of_contents.html
ब्रायन आर Bondy

1
जब SGI ने HP से STL के रखरखाव का जिम्मा लिया, तो इसे मानक पुस्तकालय से मेल खाने के लिए रेट्रो-फिट किया गया था (यही वजह है कि मैंने "HP के STL का हिस्सा नहीं था")। फिर भी, std :: string का प्रवर्तक ISO C ++ कमेटी है।
जेम्स कर्रन

2
साइड नोट: एसजीआई कर्मचारी जो कई वर्षों तक एसटीएल को बनाए रखने के प्रभारी थे, मैट ऑस्टर्न थे, जिन्होंने एक ही समय में, आईएसओ सी ++ मानकीकरण समिति के पुस्तकालय उपसमूह का नेतृत्व किया।
जेम्स कर्रन

4
क्या आप कृपया स्पष्ट कर सकते हैं या कुछ बिंदु दे सकते हैं कि आप ढेर के बजाय अपने बफ़र्स के लिए स्टैक का उपयोग क्यों कर सकते हैं जो बहुत अधिक कुशल है। ? यह दक्षता अंतर कहां से आता है?
hrr

76

पहले अपना अंतिम स्थान आरक्षित करें, फिर बफर के साथ एपेंड विधि का उपयोग करें। उदाहरण के लिए, मान लें कि आपकी अंतिम स्ट्रिंग लंबाई 1 मिलियन वर्णों की है:

std::string s;
s.reserve(1000000);

while (whatever)
{
  s.append(buf,len);
}

17

मैं इसकी चिंता नहीं करूंगा। यदि आप इसे लूप में करते हैं, तो स्ट्रिंग्स हमेशा वास्तविकताओं को कम करने के लिए मेमोरी का प्रचार करेंगे - बस operator+=उस स्थिति में उपयोग करें । और अगर आप इसे मैन्युअल रूप से करते हैं, तो कुछ ऐसा या लंबा

a + " : " + c

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

((a + " : ") + c) 
calls string operator+(string const&, char const*)(a, " : ")
  => (tmp1 + c)

अब, इसके अलावा, tmp1ऑपरेटर को पहले कॉल के द्वारा दिखाए गए तर्कों के साथ वापस लौटा दिया गया है। हम मानते हैं कि कंपाइलर वास्तव में चतुर है और रिटर्न वैल्यू कॉपी का अनुकूलन करता है। तो हम एक नया स्ट्रिंग है के संयोजन में शामिल है के साथ खत्म हो aऔर " : "। अब, यह होता है:

(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
  => tmp2 == <end result>

निम्नलिखित की तुलना करें:

std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
  => tmp1 == <end result>

यह अस्थायी और नामित स्ट्रिंग के लिए समान फ़ंक्शन का उपयोग कर रहा है! तो संकलक को तर्क को एक नए स्ट्रिंग में कॉपी करना होगा और उस पर संलग्न करना होगा और इसे शरीर से वापस करना होगा operator+। यह एक अस्थायी की स्मृति नहीं ले सकता है और उसी को जोड़ सकता है। अभिव्यक्ति जितनी बड़ी होती है, उतने ही तार की प्रतियाँ करनी पड़ती हैं।

अगला विज़ुअल स्टूडियो और GCC c ++ 1x के मूवमेंट शब्दार्थ ( कॉपी सिमेंटिक्स का पूरक ) और प्रायोगिक जोड़ के रूप में संदर्भों का समर्थन करेगा । यह पता लगाने की अनुमति देता है कि पैरामीटर अस्थायी है या नहीं। यह इस तरह के परिवर्धन को आश्चर्यजनक रूप से तेज कर देगा, क्योंकि उपरोक्त सभी प्रतियां के बिना एक "ऐड-पाइपलाइन" में समाप्त हो जाएंगे।

यदि यह एक अड़चन है, तो आप अभी भी कर सकते हैं

 std::string(a).append(" : ").append(c) ...

appendकॉल करने के लिए तर्क संलग्न *thisऔर फिर खुद के लिए एक संदर्भ लौट आते हैं। इसलिए वहां अस्थायी लोगों की कोई नकल नहीं की जाती है। या वैकल्पिक रूप से, का operator+=उपयोग किया जा सकता है, लेकिन पूर्ववर्तीता को ठीक करने के लिए आपको बदसूरत कोष्ठक की आवश्यकता होगी।


मुझे वास्तव में ऐसा करने वाले stdlib कार्यान्वयनकर्ताओं की जांच करनी थी। : पी के libstdc++लिए operator+(string const& lhs, string&& rhs)करता है return std::move(rhs.insert(0, lhs))। फिर अगर दोनों अस्थायी हैं, operator+(string&& lhs, string&& rhs)तो अगर lhsइसकी पर्याप्त क्षमता उपलब्ध है , तो यह सीधे तौर पर उपलब्ध होगी append()। जहां मुझे लगता है कि यह जोखिम की तुलना operator+=में धीमा है , अगर lhsइसके पास पर्याप्त क्षमता नहीं है, तो यह फिर से गिर जाता है rhs.insert(0, lhs), जो न केवल बफर का विस्तार करना चाहिए और नई सामग्री को जोड़ना चाहिए append(), बल्कि rhsसही की मूल सामग्री के साथ स्थानांतरित करने की आवश्यकता है ।
अंडरस्कोर_ड

इसकी तुलना में ओवरहेड का दूसरा टुकड़ा यह operator+=है कि operator+अभी भी एक मूल्य वापस करना चाहिए, इसलिए इसे जिस पर move()भी जोड़ा जाना चाहिए। फिर भी, मुझे लगता है कि पूरे स्ट्रिंग को डीप-कॉपी करने की तुलना में यह काफी मामूली ओवरहेड (कुछ बिंदुओं / आकारों की नकल) है, इसलिए यह अच्छा है!
अंडरस्कोर_ड

11

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


7
बेशक यह ज्यादातर मामलों के लिए इसके लायक नहीं है, लेकिन यह वास्तव में उसके सवाल का जवाब नहीं देता है।
ब्रायन आर बॉंडी

1
हाँ। मैं सिर्फ कह सहमत "प्रोफ़ाइल तो अनुकूलन" सवाल :) पर टिप्पणी के रूप में रखा जा सकता है
Johannes Schaub - litb

6
तकनीकी रूप से, उन्होंने पूछा कि क्या ये "आवश्यक" हैं। वे नहीं हैं, और यह उस सवाल का जवाब देता है।
सामंथा ब्रांहम

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

4
@Pesto प्रोग्रामिंग की दुनिया में एक विकृत धारणा है कि प्रदर्शन कोई मायने नहीं रखता है और हम सिर्फ पूरे सौदे को अनदेखा कर सकते हैं क्योंकि कंप्यूटर तेज हो रहे हैं। बात यह है, यही कारण है कि लोग C ++ में कार्यक्रम नहीं करते हैं और यही कारण है कि वे कुशल स्ट्रिंग संयोजन के बारे में स्टैक ओवरफ्लो पर सवाल पोस्ट नहीं करते हैं।
MrFox

7

.NET सिस्टम के विपरीत, सी + +, एस टी डी :: स्ट्रिंग्स उत्परिवर्तनीय होते हैं , और इसलिए इसे सरल रूप में अन्य तरीकों के माध्यम से उपवास के माध्यम से बनाया जा सकता है।


2
खासकर यदि आप रिज़र्व () का उपयोग शुरू करने से पहले परिणाम के लिए बफर को बड़ा बनाने के लिए करते हैं।
मार्क रैनसम

मुझे लगता है कि वह ऑपरेटर = के बारे में बात कर रहा है। यह भी पतित है, हालांकि यह एक पतित मामला है। जेम्स एक vc ++ एमवीपी था तो मैं उम्मीद वह ++ सी के कुछ सुराग है: p
litb - Johannes Schaub

1
मैं एक दूसरे के लिए संदेह नहीं करता कि उसे सी ++ पर व्यापक ज्ञान है, बस इस सवाल के बारे में गलतफहमी थी। ऑपरेटर की दक्षता के बारे में पूछे जाने वाले प्रश्न + जो हर बार नई स्ट्रिंग वस्तुओं को लौटाता है, जिसे यह कहा जाता है, और इसलिए नए चार बफ़र्स का उपयोग करता है।
ब्रायन आर बॉडी

1
हाँ। लेकिन फिर उसने केस संचालक के लिए कहा कि + धीमा है, सबसे अच्छा तरीका क्या है। और यहाँ ऑपरेटर + = खेल में आता है। लेकिन मैं सहमत हूँ कि जेम्स का जवाब थोड़ा छोटा है। p: ऐसा लगता है कि हम सभी ऑपरेटर + इस्तेमाल कर सकते हैं यह ध्वनि बनाता है और यह शीर्ष है कुशल
litb - Johannes Schaub

@ ब्रायनआर.बोंडी operator+को नया स्ट्रांग वापस नहीं करना है। कार्यान्वयनकर्ता अपने किसी एक ऑपरेंड को वापस कर सकता है, संशोधित किया जा सकता है, अगर उस ऑपरेंड को रैवल्यू रेफरेंस द्वारा पारित किया गया था। libstdc++ उदाहरण के लिए, यह करता है । इसलिए, जब टेंपरेरी के operator+साथ कॉल किया जाता है, तो यह वही या लगभग अच्छा प्रदर्शन प्राप्त कर सकता है - जो इसे डिफ़ॉल्ट करने के पक्ष में एक और तर्क हो सकता है जब तक कि कोई बेंचमार्क नहीं दिखाता है कि यह एक अड़चन का प्रतिनिधित्व करता है।
अंडरस्कोर_ड

5

शायद एसटी :: स्ट्रिंग के बजाय?

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


2
stringstream धीमी है, को देखने के groups.google.com/d/topic/comp.lang.c++.moderated/aiFIGb6za0w
ArtemGr

1
@ArtemGr स्ट्रिंगस्ट्रीम तेज हो सकता है, देखें codeproject.com/Articles/647856/…
mloskot

4

में अपूर्ण सी ++ , मैथ्यू विल्सन एक प्रस्तुत करता है गतिशील स्ट्रिंग concatenator कि सभी भागों श्रृंखलाबद्ध से पहले केवल एक ही आवंटन किया है करने के लिए अंतिम स्ट्रिंग की लंबाई पूर्व गणना करता है। हम अभिव्यक्ति टेम्पलेट्स के साथ खेलकर एक स्थिर समवर्ती को भी लागू कर सकते हैं ।

STLport std :: string कार्यान्वयन में इस तरह के विचार को लागू किया गया है - जो इस सटीक हैक के कारण मानक के अनुरूप नहीं है।


Glib::ustring::compose()glibmm बाइंडिंग से GLib तक यह होता है: reserve()प्रदान किए गए प्रारूप स्ट्रिंग और varargs के आधार पर अंतिम लंबाई का अनुमान लगाते हैं और फिर append()प्रत्येक (या उसके स्वरूपित प्रतिस्थापन) को लूप में रखते हैं। मुझे उम्मीद है कि यह काम करने का एक बहुत ही सामान्य तरीका है।
अंडरस्कोर_ड

4

std::string operator+एक नया स्ट्रिंग आवंटित करता है और हर बार दो ऑपरेंड स्ट्रिंग्स को कॉपी करता है। कई बार दोहराएं और यह महंगा हो जाता है, ओ (एन)।

std::string appendऔर operator+=दूसरी ओर, स्ट्रिंग को बढ़ने के लिए हर बार क्षमता को 50% तक उछालें। जो स्मृति आबंटनों की संख्या कम कर देता है और संचालन को कॉपी करता है, हे (लॉग एन)।


मुझे यकीन नहीं है कि यह क्यों ठुकरा दिया गया था। मानक द्वारा 50% का आंकड़ा आवश्यक नहीं है, लेकिन IIRC कि या 100% व्यवहार में वृद्धि के सामान्य उपाय हैं। इस उत्तर में बाकी सब कुछ अविश्वसनीय लगता है।
अंडरस्कोर_ड

महीनों बाद, मुझे लगता है कि यह सब सटीक नहीं है, क्योंकि यह C ++ 11 के शुरू होने के लंबे समय बाद लिखा गया था, और operator+जहां एक या दोनों तर्क तर्क संदर्भ द्वारा पारित किए गए हैं , के अधिभार मौजूदा बफर में समाप्‍त करके एक नया स्ट्रिंग पूरी तरह से आवंटित करने से बच सकते हैं। ऑपरेंड्स में से एक (यद्यपि वे अपर्याप्त होने की स्थिति में वास्तविक हो सकते हैं)।
अंडरस्कोर_ड

2

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

मैं एसटीडी पसंद करता हूं :: जटिल संघनन के लिए ओस्ट्रिंगस्ट्रीम।


2

अधिकांश चीजों की तरह, इसे करने की तुलना में कुछ करना आसान नहीं है।

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

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

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


2

यदि आप परिणामी स्ट्रिंग में पूर्व-आरक्षित (आरक्षित) स्थान रखते हैं तो संभवतः सर्वश्रेष्ठ प्रदर्शन।

template<typename... Args>
std::string concat(Args const&... args)
{
    size_t len = 0;
    for (auto s : {args...})  len += strlen(s);

    std::string result;
    result.reserve(len);    // <--- preallocate result
    for (auto s : {args...})  result += s;
    return result;
}

उपयोग:

std::string merged = concat("This ", "is ", "a ", "test!");

0

वर्णों का एक सरल सरणी, एक वर्ग में समझाया गया है जो सरणी आकार का ट्रैक रखता है और आवंटित बाइट्स की संख्या सबसे तेज़ है।

चाल शुरू में सिर्फ एक बड़ा आवंटन करना है।

पर

https://github.com/pedro-vicente/table-string

मानक

विजुअल स्टूडियो 2015 के लिए, x86 डीबग बिल्ड, C ++ std :: string पर वित्तीय सुधार।

| API                   | Seconds           
| ----------------------|----| 
| SDS                   | 19 |  
| std::string           | 11 |  
| std::string (reserve) | 9  |  
| table_str_t           | 1  |  

1
ओपी को दिलचस्पी है कि कैसे कुशलतापूर्वक सम्मेलन में भाग लिया जाए std::string। वे एक वैकल्पिक स्ट्रिंग वर्ग के लिए नहीं पूछ रहे हैं।
अंडरस्कोर_ड

0

आप प्रत्येक आइटम के लिए स्मृति आरक्षण के साथ इसे आज़मा सकते हैं:

namespace {
template<class C>
constexpr auto size(const C& c) -> decltype(c.size()) {
  return static_cast<std::size_t>(c.size());
}

constexpr std::size_t size(const char* string) {
  std::size_t size = 0;
  while (*(string + size) != '\0') {
    ++size;
  }
  return size;
}

template<class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept {
  return N;
}
}

template<typename... Args>
std::string concatStrings(Args&&... args) {
  auto s = (size(args) + ...);
  std::string result;
  result.reserve(s);
  return (result.append(std::forward<Args>(args)), ...);
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.