Libc ++ में शॉर्ट स्ट्रिंग ऑप्टिमाइज़ेशन के मैकेनिक्स क्या हैं?


102

यह उत्तर शॉर्ट स्ट्रिंग ऑप्टिमाइज़ेशन (SSO) का अच्छा उच्च-स्तरीय अवलोकन देता है। हालांकि, मैं अधिक विस्तार से जानना चाहूंगा कि यह व्यवहार में कैसे काम करता है, विशेष रूप से libc ++ कार्यान्वयन में:

  • SSO के लिए अर्हता प्राप्त करने के लिए स्ट्रिंग को कितना छोटा होना चाहिए? क्या यह लक्ष्य वास्तुकला पर निर्भर करता है?

  • स्ट्रिंग डेटा तक पहुँचने के दौरान कार्यान्वयन छोटे और लंबे तारों के बीच कैसे भेद करता है? क्या यह उतना ही सरल है m_size <= 16या यह एक झंडा है जो किसी अन्य सदस्य चर का हिस्सा है? (मुझे लगता है कि m_sizeया इसका एक हिस्सा भी स्ट्रिंग डेटा स्टोर करने के लिए इस्तेमाल किया जा सकता है)।

मैंने यह सवाल विशेष रूप से libc ++ के लिए पूछा क्योंकि मुझे पता है कि यह SSO का उपयोग करता है, यह भी libc ++ होम पेज पर उल्लिखित है ।

स्रोत को देखने के बाद यहाँ कुछ अवलोकन दिए गए हैं :

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

यह मानकर कि size_type4 बाइट्स और वह value_type1 बाइट है, यह वह है जो एक स्ट्रिंग के पहले 4 बाइट्स मेमोरी में दिखेगी:

// short string: (s)ize and 3 bytes of char (d)ata
sssssss0;dddddddd;dddddddd;dddddddd
       ^- is_long = 0

// long string: (c)apacity
ccccccc1;cccccccc;cccccccc;cccccccc
       ^- is_long = 1

चूंकि शॉर्ट स्ट्रिंग का आकार ऊपरी 7 बिट्स में है, इसे एक्सेस करते समय इसे स्थानांतरित करने की आवश्यकता होती है:

size_type __get_short_size() const {
    return __r_.first().__s.__size_ >> 1;
}

इसी तरह, लंबे स्ट्रिंग की क्षमता के लिए गेटटर और सेटर बिट के __long_maskआसपास काम करने के लिए उपयोग करता है is_long

मैं अभी भी अपने पहले प्रश्न के उत्तर की तलाश कर रहा हूं, अर्थात __min_capछोटे तारों की क्षमता, विभिन्न आर्किटेक्चर के लिए क्या मूल्य होगा ?

अन्य मानक पुस्तकालय कार्यान्वयन

यह उत्तरstd::string अन्य मानक पुस्तकालय कार्यान्वयन में स्मृति लेआउट का एक अच्छा अवलोकन देता है ।


libc ++ ओपन-सोर्स होने के कारण, आप इसका stringहेडर यहां पा सकते हैं , मैं इसे फिलहाल देख रहा हूं :)
Matthieu M.


@ माथिउ एम।: मैंने देखा था कि पहले, दुर्भाग्य से यह एक बहुत बड़ी फाइल है, इसे जाँचने में मदद के लिए धन्यवाद।
वलारडोहेरिस

@ अलि: मैं चारों ओर googling में इस पर ठोकर खाई है। हालाँकि, यह ब्लॉग पोस्ट स्पष्ट रूप से कहती है कि यह केवल SSO का चित्रण है न कि एक अत्यधिक अनुकूलित संस्करण जो व्यवहार में उपयोग किया जाएगा।
वलारडोहेरिस

जवाबों:


120

Libc ++ basic_stringको sizeofसभी आर्किटेक्चर पर 3 शब्दों में बनाया गया है , जहां sizeof(word) == sizeof(void*)। आपने लंबे / छोटे ध्वज, और छोटे रूप में आकार फ़ील्ड को सही ढंग से विच्छेदित किया है।

क्या मूल्य __min_cap, छोटे तारों की क्षमता, विभिन्न आर्किटेक्चर के लिए ले जाएगा?

संक्षिप्त रूप में, इसके साथ काम करने के लिए 3 शब्द हैं:

  • 1 बिट लंबे / छोटे ध्वज पर जाता है।
  • 7 बिट्स आकार में जाता है।
  • मान लें char, 1 बाइट अनुगामी नल के पास जाता है (libc ++ हमेशा डेटा के पीछे एक अनुगामी नल संचित करेगा)।

यह 3 शब्द माइनस 2 बाइट्स को एक छोटी स्ट्रिंग ( capacity()एक आवंटन के बिना सबसे बड़ा ) स्टोर करने के लिए छोड़ देता है ।

32 बिट मशीन पर, 10 तार शॉर्ट स्ट्रिंग में फिट होंगे। sizeof (स्ट्रिंग) 12 है।

64 बिट मशीन पर, 22 तार शॉर्ट स्ट्रिंग में फिट होंगे। sizeof (स्ट्रिंग) 24 है।

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

डेटा पॉइंटर, आकार और क्षमता को संग्रहीत करने के लिए लंबे फॉर्म में न्यूनतम 3 शब्दों की आवश्यकता होती है। इसलिए मैंने उन्हीं 3 शब्दों के संक्षिप्त रूप को प्रतिबंधित कर दिया। यह सुझाव दिया गया है कि 4 शब्द आकार का प्रदर्शन बेहतर हो सकता है। मैंने उस डिज़ाइन विकल्प का परीक्षण नहीं किया है।

_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT

एक कॉन्फ़िगरेशन फ़्लैग कहा जाता है _LIBCPP_ABI_ALTERNATE_STRING_LAYOUTजो डेटा सदस्यों को इस तरह से पुनर्व्यवस्थित करता है कि "लॉन्ग लेआउट" इससे बदल जाता है:

struct __long
{
    size_type __cap_;
    size_type __size_;
    pointer   __data_;
};

सेवा:

struct __long
{
    pointer   __data_;
    size_type __size_;
    size_type __cap_;
};

इस बदलाव की प्रेरणा यह धारणा है कि __data_पहले से बेहतर प्रदर्शन के कारण कुछ प्रदर्शन फायदे होंगे। प्रदर्शन के लाभों को मापने का प्रयास किया गया था, और इसे मापना मुश्किल था। यह प्रदर्शन को बदतर नहीं करेगा, और यह इसे थोड़ा बेहतर बना सकता है।

ध्वज का उपयोग सावधानी से किया जाना चाहिए। यह एक अलग ABI है, और अगर गलती से एक libc ++ के साथ मिश्रित होता है, तो std::stringएक अलग सेटिंग के साथ संकलित _LIBCPP_ABI_ALTERNATE_STRING_LAYOUTरन टाइम त्रुटियाँ पैदा करेगा।

मैं इस ध्वज को केवल libc ++ के एक विक्रेता द्वारा परिवर्तित करने की सलाह देता हूं।


17
यह निश्चित नहीं है कि libc ++ और Facebook Folly के बीच लाइसेंस संगतता है, लेकिन FBstring शेष क्षमता में आकार बदलकर एक अतिरिक्त चार (यानी 23) स्टोर करने का प्रबंधन करता है , ताकि यह 23 वर्णों की छोटी स्ट्रिंग के लिए शून्य टर्मिनेटर के रूप में डबल ड्यूटी कर सके ।
टेम्पलेटरैक्स

20
@TemplateRex: यह चतुर है। हालाँकि, अगर libc ++ इसे अपनाता है तो libc ++ को एक अन्य विशेषता को छोड़ना होगा जो मुझे इसके std :: string के बारे में पसंद है: एक डिफ़ॉल्ट निर्मित stringसभी 0 बिट्स है। यह डिफ़ॉल्ट निर्माण को सुपर कुशल बनाता है। और यदि आप नियमों को मोड़ने के लिए तैयार हैं, तो कभी-कभी मुफ्त भी। उदाहरण के लिए, आप callocमेमोरी कर सकते हैं और बस इसे डिफ़ॉल्ट रूप से निर्मित स्ट्रिंग्स से भरा घोषित कर सकते हैं।
हावर्ड हिनान्ट

6
आह, 0-init वास्तव में अच्छा है! BTW, FBstring में 2 फ्लैग बिट्स हैं, जो छोटे, मध्यवर्ती और बड़े तारों का संकेत देते हैं। यह 23 वर्णों तक स्ट्रिंग्स के लिए SSO का उपयोग करता है, और फिर 254 वर्णों तक तार के लिए एक मॉलोक-एड मेमोरी क्षेत्र का उपयोग करता है और इसके अलावा वे गाय करते हैं (अब C ++ 11 में कानूनी नहीं है, मुझे पता है)।
TemplateRex

आकार और क्षमता को क्यों संग्रहीत नहीं किया जा सकता है intताकि वर्ग 64-बिट आर्किटेक्चर पर केवल 16 बाइट्स में पैक किया जा सके?
फुल्विक

@ LưuV LnhPhúc: मैं 64-बिट पर 2Gb से अधिक तारों की अनुमति देना चाहता था। लागत वास्तव में एक बड़ा है sizeof। लेकिन एक ही समय में आंतरिक बफर char14 से 22 के लिए चला जाता है, जो एक बहुत अच्छा लाभ है।
हॉवर्ड हिनेंट

21

Libc ++ कार्यान्वयन थोड़ा जटिल है, मैं इसकी वैकल्पिक डिजाइन पर ध्यान न दें और एक छोटे से endian कंप्यूटर लगता होगी:

template <...>
class basic_string {
/* many many things */

    struct __long
    {
        size_type __cap_;
        size_type __size_;
        pointer   __data_;
    };

    enum {__short_mask = 0x01};
    enum {__long_mask  = 0x1ul};

    enum {__min_cap = (sizeof(__long) - 1)/sizeof(value_type) > 2 ?
                      (sizeof(__long) - 1)/sizeof(value_type) : 2};

    struct __short
    {
        union
        {
            unsigned char __size_;
            value_type __lx;
        };
        value_type __data_[__min_cap];
    };

    union __ulx{__long __lx; __short __lxx;};

    enum {__n_words = sizeof(__ulx) / sizeof(size_type)};

    struct __raw
    {
        size_type __words[__n_words];
    };

    struct __rep
    {
        union
        {
            __long  __l;
            __short __s;
            __raw   __r;
        };
    };

    __compressed_pair<__rep, allocator_type> __r_;
}; // basic_string

नोट: __compressed_pairअनिवार्य रूप से खाली बेस ऑप्टिमाइज़ेशन , उर्फ ​​के लिए अनुकूलित एक जोड़ी है template <T1, T2> struct __compressed_pair: T1, T2 {};; सभी इरादों और उद्देश्यों के लिए आप इसे एक नियमित जोड़ी मान सकते हैं। इसका महत्व सिर्फ इसलिए सामने आता है क्योंकि std::allocatorयह स्टेटलेस है और इस तरह खाली है।

ठीक है, यह बल्कि कच्चा है, तो चलो यांत्रिकी की जांच करें! आंतरिक रूप से, कई फ़ंक्शन कॉल करेंगे __get_pointer()जो स्वयं __is_longयह निर्धारित करने के लिए कॉल करता है कि स्ट्रिंग __longया __shortप्रतिनिधित्व का उपयोग कर रहा है :

bool __is_long() const _NOEXCEPT
    { return bool(__r_.first().__s.__size_ & __short_mask); }

// __r_.first() -> __rep const&
//     .__s     -> __short const&
//     .__size_ -> unsigned char

ईमानदार होने के लिए, मुझे यह सुनिश्चित नहीं है कि यह मानक C ++ है (मैं प्रारंभिक अनुवर्ती प्रावधान को unionजानता हूं लेकिन यह नहीं जानता कि यह एक अनाम संघ और एक साथ फेंके जाने वाले अलियासिंग के साथ कैसे होता है), लेकिन एक मानक पुस्तकालय को परिभाषित कार्यान्वयन का लाभ लेने की अनुमति है वैसे भी व्यवहार।


इस विस्तृत उत्तर के लिए धन्यवाद! एकमात्र टुकड़ा जो मुझे याद आ रहा है वह __min_capयह है कि अलग-अलग आर्किटेक्चर के लिए मूल्यांकन किया जाएगा, मुझे यकीन नहीं है कि क्या sizeof()वापस आएगा और यह एलिंगिंग से कैसे प्रभावित होता है।
वलारडोहेरिस

1
@ValarDohaeris यह कार्यान्वयन परिभाषित है। आमतौर पर, आप 3 * the size of one pointerइस मामले में उम्मीद करेंगे , जो कि 32 बिट आर्क पर 12 ओकटेट और 64 बिट आर्क पर 24 होगा।
जस्टिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.