वास्तव में std :: string_view const std :: string & से अधिक तेज कैसे है?


221

std::string_viewने इसे C ++ 17 में बनाया है और इसके बजाय इसका उपयोग करने के लिए व्यापक रूप से अनुशंसा की जाती है const std::string&

कारणों में से एक प्रदर्शन है।

क्या कोई समझा सकता है कि पैरामीटर प्रकार के रूप में उपयोग किए जाने की तुलना में वास्तव में std::string_view यह कितना तेज़ है / होगा const std::string&? (मान लें कि कैली में कोई प्रतियां नहीं हैं)


7
std::string_viewसिर्फ एक अमूर्त है (चार * शुरू, चार * अंत) जोड़ी। आप इसका उपयोग तब करते हैं जब std::stringएक अनावश्यक प्रतिलिपि होगी।
प्रश्न

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

@QuestionC आप इसका उपयोग तब करते हैं जब आप नहीं चाहते कि आपका API प्रतिबंधित हो std::string(string_view कच्चे सरणियों, वैक्टरों को, std::basic_string<>गैर-डिफ़ॉल्ट आवंटनकर्ता आदि के साथ स्वीकार कर सकता है ) आदि ओह, और अन्य string_views स्पष्ट रूप से)
sehe

जवाबों:


213

std::string_view कुछ मामलों में तेज है।

सबसे पहले, std::string const&डेटा की आवश्यकता होती है std::string, और एक कच्चे सी सरणी नहीं, char const*सी एपीआई द्वारा लौटाया जाता है, std::vector<char>कुछ डीरिएरलाइजेशन इंजन द्वारा निर्मित, आदि से बचा जाता है, बचा हुआ प्रारूप रूपांतरण बाइट्स की नकल करने से बचता है, और यदि स्ट्रिंग की तुलना में लंबा है। SBO S विशेष std::stringकार्यान्वयन के लिए) एक स्मृति आवंटन से बचा जाता है।

void foo( std::string_view bob ) {
  std::cout << bob << "\n";
}
int main(int argc, char const*const* argv) {
  foo( "This is a string long enough to avoid the std::string SBO" );
  if (argc > 1)
    foo( argv[1] );
}

string_viewमामले में कोई आवंटन नहीं किया जाता है , लेकिन अगर इसके बजाय fooलिया जाएगा ।std::string const&string_view

दूसरा वास्तव में बड़ा कारण यह है कि यह बिना कॉपी के सबस्ट्रिंग के साथ काम करने की अनुमति देता है। मान लीजिए कि आप एक 2 गीगाबाइट जोंस स्ट्रिंग (!) Ose पार्स कर रहे हैं। यदि आप इसे पार्स करते हैं std::string, तो प्रत्येक ऐसे पार्स नोड जहां वे नोड के नाम या मूल्य को संग्रहीत करते हैं , मूल डेटा को 2 जीबी स्ट्रिंग से एक स्थानीय नोड में कॉपी करते हैं।

इसके बजाय, यदि आप इसे std::string_views पर पार्स करते हैं , तो नोड्स मूल डेटा को संदर्भित करते हैं। यह पार्स करने के दौरान लाखों आवंटन बचा सकता है और मेमोरी आवश्यकताओं को आधा कर सकता है।

स्पीडअप आपको मिल सकता है बस हास्यास्पद है।

यह एक चरम मामला है, लेकिन अन्य "एक विकल्प प्राप्त करते हैं और इसके साथ काम करते हैं" मामलों के साथ सभ्य स्पीडअप भी उत्पन्न कर सकते हैं string_view

निर्णय का एक महत्वपूर्ण हिस्सा वह है जो आप उपयोग करके खो देते हैं std::string_view। यह ज्यादा नहीं है, लेकिन यह कुछ है।

आप निहित शून्य समाप्ति खो देते हैं, और यह इसके बारे में है। तो अगर एक ही स्ट्रिंग को 3 कार्यों के लिए पारित किया जाएगा, जिसमें सभी को एक शून्य टर्मिनेटर की आवश्यकता होती है, जो एक std::stringबार बुद्धिमान हो सकता है। इस प्रकार यदि आपके कोड को एक शून्य टर्मिनेटर की आवश्यकता के लिए जाना जाता है, और आप सी-शैली के खट्टे बफ़र्स या इस तरह से खिलाए गए तार की उम्मीद नहीं करते हैं, तो शायद एक ले लें std::string const&। नहीं तो ए std::string_view

यदि std::string_viewएक ध्वज था जो कहा गया था कि यदि इसे समाप्त कर दिया गया था (या कुछ कट्टर) तो यह उस अंतिम उपयोग को भी हटा देगा std::string const&

एक ऐसा मामला है जहां std::stringकोई नहीं के साथ एक const&इष्टतम पर एक है std::string_view। यदि आपको कॉल के बाद अनिश्चित रूप से स्ट्रिंग की एक प्रति के स्वामी होने की आवश्यकता है, तो बाय-वैल्यू लेना कुशल है। आप या तो एसबीओ मामले में होंगे (और कोई आवंटन नहीं, बस कुछ चरित्र प्रतियां इसे डुप्लिकेट करने के लिए), या आप हीप-आवंटित बफर को एक स्थानीय में स्थानांतरित करने में सक्षम होंगे std::string। दो ओवरलोड होने std::string&&और std::string_viewतेज हो सकते हैं, लेकिन केवल मामूली रूप से, और यह मामूली कोड ब्लोट का कारण होगा (जो आपको गति के सभी लाभ प्राप्त कर सकता है)।


¹ छोटे बफर अनुकूलन

Case वास्तविक उपयोग का मामला।


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

SBO अजीब लगता है। मैंने हमेशा एसएसओ (छोटे स्ट्रिंग ऑप्टिमाइज़ेशन) को सुना है
phuclv

@ एफयू ज़रूर; लेकिन तार केवल एक चीज नहीं है जिस पर आप चाल का उपयोग करते हैं।
यक्क - एडम नेवरामोंट

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

59

String_view के प्रदर्शन को बेहतर बनाने वाला एक तरीका यह है कि यह उपसर्गों और प्रत्ययों को आसानी से हटाने की अनुमति देता है। हुड के तहत, स्ट्रिंग_व्यू केवल उपसर्ग आकार को कुछ स्ट्रिंग बफ़र को सूचक में जोड़ सकता है, या बाइट काउंटर से प्रत्यय आकार को घटा सकता है, यह आमतौर पर तेज़ होता है। std :: दूसरी ओर स्ट्रिंग को इसकी बाइट्स की नकल करनी होती है जब आप कुछ ऐसा करते हैं जैसे कि पदार्थ (इस तरह से आपको एक नया स्ट्रिंग मिलता है जो इसके बफर का होता है, लेकिन कई मामलों में आप सिर्फ कॉपी किए बिना मूल स्ट्रिंग का हिस्सा प्राप्त करना चाहते हैं)। उदाहरण:

std::string str{"foobar"};
auto bar = str.substr(3);
assert(bar == "bar");

Std :: string_view के साथ:

std::string str{"foobar"};
std::string_view bar{str.c_str(), str.size()};
bar.remove_prefix(3);
assert(bar == "bar");

अपडेट करें:

मैंने कुछ वास्तविक संख्याओं को जोड़ने के लिए एक बहुत ही सरल बेंचमार्क लिखा। मैंने कमाल की बेंचमार्क लाइब्रेरी का इस्तेमाल किया । बेंचमार्क किए गए कार्य हैं:

string remove_prefix(const string &str) {
  return str.substr(3);
}
string_view remove_prefix(string_view str) {
  str.remove_prefix(3);
  return str;
}
static void BM_remove_prefix_string(benchmark::State& state) {                
  std::string example{"asfaghdfgsghasfasg3423rfgasdg"};
  while (state.KeepRunning()) {
    auto res = remove_prefix(example);
    // auto res = remove_prefix(string_view(example)); for string_view
    if (res != "aghdfgsghasfasg3423rfgasdg") {
      throw std::runtime_error("bad op");
    }
  }
}
// BM_remove_prefix_string_view is similar, I skipped it to keep the post short

परिणाम

(x86_64 linux, gcc 6.2, " -O3 -DNDEBUG"):

Benchmark                             Time           CPU Iterations
-------------------------------------------------------------------
BM_remove_prefix_string              90 ns         90 ns    7740626
BM_remove_prefix_string_view          6 ns          6 ns  120468514

2
यह बहुत अच्छा है कि आपने वास्तविक बेंचमार्क प्रदान किया। यह वास्तव में दिखाता है कि प्रासंगिक उपयोग के मामलों में क्या प्राप्त किया जा सकता है।
डैनियल कामिल कोजार

1
@DanielKamilKozar प्रतिक्रिया के लिए धन्यवाद। मुझे भी लगता है कि बेंचमार्क मूल्यवान हैं, कभी-कभी वे सब कुछ बदल देते हैं।
पावेल डेविडोव

47

इसके 2 मुख्य कारण हैं:

  • string_view मौजूदा बफर में एक स्लाइस है, इसे मेमोरी आवंटन की आवश्यकता नहीं है
  • string_view मान से पारित किया जाता है, संदर्भ से नहीं

एक टुकड़ा होने के फायदे कई हैं:

  • आप एक नया बफर आवंटित किए बिना char const*या उसके साथ इसका उपयोग कर सकते हैंchar[]
  • आप कई स्लाइस ले सकते हैं और बिना आवंटन के एक मौजूदा बफर में सब्सक्राइब कर सकते हैं
  • सबस्ट्रिंग O (1) है, O (N) नहीं
  • ...

सभी पर बेहतर और अधिक लगातार प्रदर्शन।


मूल्य से गुजरना भी संदर्भ से गुजरने पर फायदे हैं, क्योंकि उपनाम।

विशेष रूप से, जब आपके पास कोई std::string const&पैरामीटर होता है, तो इस बात की कोई गारंटी नहीं है कि संदर्भ स्ट्रिंग को संशोधित नहीं किया जाएगा। नतीजतन, कंपाइलर को एक अपारदर्शी विधि (डेटा, लंबाई, ...) के लिए प्रत्येक कॉल के बाद स्ट्रिंग की सामग्री को फिर से प्राप्त करना होगा।

दूसरी ओर, जब एक string_viewमान गुजरता है , तो संकलक सांख्यिकीय रूप से यह निर्धारित कर सकता है कि स्टैक पर (या रजिस्टरों में) अब कोई अन्य कोड लंबाई और डेटा पॉइंटर्स को संशोधित नहीं कर सकता है। नतीजतन, यह उन्हें फ़ंक्शन कॉल के दौरान "कैश" कर सकता है।


36

एक काम यह कर सकता है std::stringएक शून्य समाप्त स्ट्रिंग से एक अंतर्निहित रूपांतरण के मामले में एक वस्तु का निर्माण करने से बचें :

void foo(const std::string& s);

...

foo("hello, world!"); // std::string object created, possible dynamic allocation.
char msg[] = "good morning!";
foo(msg); // std::string object created, possible dynamic allocation.

12
यह कहने लायक हो सकता है कि const std::string str{"goodbye!"}; foo(str);शायद स्ट्रिंग के साथ string_view के साथ कोई तेज नहीं होगा
मार्टिन बोनर मोनिका

1
के string_viewरूप में यह एक सूचक में विरोध के रूप में दो संकेत की नकल करने के लिए धीमी हो गई है const string&?
बाल्की

9

std::string_viewमूल रूप से एक रैपर है const char*। और पासिंग का const char*मतलब है कि पासिंग const string*(या const string&) की तुलना में सिस्टम में एक कम पॉइंटर होगा , क्योंकि string*इसका मतलब कुछ है:

string* -> char* -> char[]
           |   string    |

स्पष्ट रूप से कॉन्स्टैंट दलील पास करने के उद्देश्य से पहला पॉइंटर बहुत ही शानदार है।

ps एक std::string_viewऔर के बीच const char*एक वित्तीय अंतर , फिर भी, यह है कि string_ साक्षात्कार को शून्य-समाप्त होने की आवश्यकता नहीं है (उनके पास अंतर्निहित आकार है), और यह लंबे समय तक तार के यादृच्छिक इन-प्लेस की अनुमति देता है।


4
नीचे के साथ क्या है? std::string_viewएस सिर्फ फैंसी const char*एस, पीरियड हैं। जीसीसी उन्हें इस तरह से लागू करता है:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
n.caillou

4
बस 65K प्रतिनिधि (अपने वर्तमान 65 से) को प्राप्त करें और यह स्वीकृत उत्तर होगा (कार्गो-पंथ की भीड़ के लिए लहरें) :)
mlvljr

7
@mlvljr कोई भी नहीं गुजरता std::string const*। और वह आरेख अचूक है। @ n.caillou: आपकी अपनी टिप्पणी पहले से ही उत्तर की तुलना में अधिक सटीक है। यह string_view"फैंसी char const*" से अधिक बनाता है - यह वास्तव में काफी स्पष्ट है।
6

@ मुझे लगता है कि कोई भी हो सकता है, कोई समस्या नहीं (यानी एक
संवेदी

2
@ आप यह समझते हैं कि अनुकूलन या निष्पादन के दृष्टिकोण से, std::string const*और std::string const&वही हैं, क्या आप नहीं?
n.illillou
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.