विषय पर बढ़ें

अंतिम अद्यतन:2026-10-12

STL खाली बेस क्लास ऑप्टिमाइज़ेशन EBO

0.प्रस्तावना

EBO, Empty Base Optimization का संक्षिप्त रूप है।

यह अनुभाग खाली क्लास (empty class) से शुरू होकर, STL के भीतर तक, फिर टेस्टिंग तक, और फिर स्वयं एक EBO कार्यान्वित करने, प्रदर्शन की तुलना, अंत में पुनः टेस्ट तथा सारांश तक जाता है।

1.खाली क्लास

एक खाली क्लास परिभाषित करते हैं: ऐसी क्लास जिसमें कोई मेंबर वेरिएबल नहीं, कोई इनहेरिटेंस नहीं, कोई डेटा एलिमेंट नहीं होता।

class Empty{
public:
    void print() {
        std::cout<<"I am Empty class"<<std::endl;
    }
};

चूँकि यह खाली है, तो इसका sizeof कितना होगा?

std::cout<<sizeof(Empty)<<std::endl; //1

परिणाम 1 है, यह खाली है तो 0 क्यों नहीं है?

क्योंकि खाली क्लास को भी इंस्टैंशिएट किया जा सकता है, और प्रत्येक इंस्टेंस का मेमोरी में एक अद्वितीय पता होता है। यह उद्देश्य पूरा करने के लिए कंपाइलर प्रायः खाली क्लास को अंतर्निहित रूप से एक बाइट जोड़ देता है, जिससे खाली क्लास इंस्टैंशिएट होने के बाद मेमोरी में अद्वितीय पता प्राप्त करती है। इसीलिए उपरोक्त आकार 1 है.

उपरोक्त उत्तर से संभवतः एक और प्रश्न उपजेगा: दो भिन्न ऑब्जेक्ट्स के पते भिन्न क्यों होने चाहिए?

अब नीचे दिया गया उदाहरण देखिए:

class Empty{
public:
    void print() {
        std::cout<<"I am Empty class"<<std::endl;
    }
};
template < typename T >
bool isSame( T const & t1, T const & t2 )
{
    return &t1 == &t2;
}

आइए इसका परीक्षण करें:

int main() {
    Empty a,b;
    assert(!isSame(a,b));  // 编译通过,a与b的地址不同

    Empty *p=new Empty;
    Empty *q=new Empty;
    assert(!isSame(p,q)); // 编译通过,a与b的地址不同
    return 0;
}

ऊपर परीक्षण से पुष्टि हुई कि दो भिन्न ऑब्जेक्ट्स के पते भिन्न होते हैं। अब नीचे दी गई परिस्थिति पर विचार करें:

Empty a,b;
// 在同一地址具有两个对象将意味着在使用指针引用它们时将无法区分这两个对象。
Empty *p1=&a;
p1->print();

यहाँ पाया जाएगा कि यदि a का पता b के पते के समान हो, तो एक ही पते पर दो ऑब्जेक्ट्स का होना यह अर्थ निकालेगा कि उन्हें पॉइंटर से रेफर करते समय दोनों ऑब्जेक्ट्स में भेद नहीं किया जा सकेगा। इसीलिए दो भिन्न ऑब्जेक्ट्स के पते भिन्न होते हैं।

2.खाली बेस क्लास ऑप्टिमाइज़ेशन

अब नीचे दिए गए दो उपयोग-तरीकों की तुलना करते हैं। पहला: एक क्लास के भीतर दूसरी क्लास को मेंबर के रूप में रखा जाता है, और फिर उसके माध्यम से समाहित क्लास की कार्यक्षमता प्राप्त की जाती है।

class notEbo  {
    int i;
    Empty e;
    // do other things
};

दूसरा: सीधे इनहेरिटेंस का तरीका अपनाकर बेस क्लास के मेंबर फंक्शन्स तथा अन्य कार्यक्षमताएँ प्राप्त की जाती हैं।

class ebo:public Empty {
    int i;
    // do other things
};

अब एक परीक्षण करते हैं:

std::cout<<sizeof(notEbo)<<std::endl;
std::cout<<sizeof(ebo)<<std::endl;

आउटपुट:

8 
4

पहले तरीके में, बाइट अलाइनमेंट (byte alignment) के कारण मूलतः केवल 1 बाइट घेरने वाले एलिमेंट को 4 के गुणज तक बढ़ा दिया जाता है, और अंत में आकार 8 बाइट्स हो जाता है।

दोनों की तुलना से पाया गया कि दूसरा तरीका — इनहेरिटेंस द्वारा बेस क्लास की कार्यक्षमता पाना — कोई अतिरिक्त आकार उत्पन्न नहीं करता; इसी ऑप्टिमाइज़ेशन को EBO (खाली बेस क्लास ऑप्टिमाइज़ेशन) कहा जाता है।

अब हम STL सोर्स कोड पर लौटते हैं और इसके उपयोग को देखते हैं!

3.STL के भीतर EBO की दुनिया

deque, rb_tree, list आदि कंटेनर — कोई भी हो — मेमोरी प्रबंधन के बिना नहीं चल सकते। इन कंटेनरों में संगत मेमोरी प्रबंधन शामिल है, और _xx_impl के माध्यम से नीचे दी गई क्लासों को इनहेरिट किया जाता है:

std::allocator<_Tp>
__gnu_cxx::bitmap_allocator<_Tp>
__gnu_cxx::bitmap_allocator<_Tp>
__gnu_cxx::__mt_alloc<_Tp>
__gnu_cxx::__pool_alloc<_Tp>
__gnu_cxx::malloc_allocator<_Tp>

तो इसका हमारे EBO से क्या लेना-देना है?

वास्तव में, ऊपर सूचीबद्ध इनहेरिट की गई बेस क्लासें सभी मेमोरी प्रबंधन की EBO (खाली बेस क्लास) हैं।

प्रत्येक कंटेनर में उपयोग के समय प्रत्येक मेमोरी प्रबंधन का rebind<_Tp>::other ही कॉल होता है।

उदाहरण के लिए, रेड-ब्लैक ट्री का सोर्स कोड स्ट्रक्चर:

typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Rb_tree_node<_Val> >::other _Node_allocator;
struct _Rb_tree_impl : public _Node_allocator
{
// do somethings
};

अब ऊपर सूचीबद्ध मेमोरी प्रबंधन क्लासों के भीतर का सोर्स कोड स्ट्रक्चर देखते हैं: यहाँ allocator का उदाहरण लेते हैं:

template<typename _Tp>
class allocator: public __allocator_base<_Tp>
{
     template<typename _Tp1>
     struct rebind { typedef allocator<_Tp1> other; };
};

देखा? rebind<_Tp>::other के माध्यम से पास किया गया मेमोरी एलोकेटर प्राप्त होता है — अर्थात् वही जो आगे उल्लिखित हैं।

std::allocator<_Tp>
__gnu_cxx::bitmap_allocator<_Tp>
__gnu_cxx::bitmap_allocator<_Tp>
__gnu_cxx::__mt_alloc<_Tp>
__gnu_cxx::__pool_alloc<_Tp>
__gnu_cxx::malloc_allocator<_Tp>

ये समझ लेने के बाद, एक टेस्ट करते हैं:

void print() {
    cout<<sizeof(std::allocator<int>)<<" "<<sizeof(std::allocator<int>::rebind<int>::other)<<endl;
    cout<<sizeof(__gnu_cxx::bitmap_allocator<int>)<<" "<<sizeof(__gnu_cxx::bitmap_allocator<int>::rebind<int>::other)<<endl;
    cout<<sizeof(__gnu_cxx::new_allocator<int>)<<" "<<sizeof(__gnu_cxx::new_allocator<int>::rebind<int>::other)<<endl;
    cout<<sizeof(__gnu_cxx::__mt_alloc<int>)<<" "<<sizeof(__gnu_cxx::__mt_alloc<int>::rebind<int>::other)<<endl;
    cout<<sizeof(__gnu_cxx::__pool_alloc<int>)<<" "<<sizeof(__gnu_cxx::__pool_alloc<int>::rebind<int>::other)<<endl;
    cout<<sizeof(__gnu_cxx::malloc_allocator<int>)<<" "<<sizeof(__gnu_cxx::malloc_allocator<int>::rebind<int>::other)<<endl;
}

आइए जाँचें कि इनका sizeof 1 है या नहीं — परीक्षण के बाद आउटपुट इस प्रकार है:

1 1
1 1
1 1
1 1
1 1
1 1

इससे स्पष्ट है कि मेमोरी प्रबंधन का कार्यान्वयन इनहेरिटेंस का तरीका अपनाकर, खाली बेस क्लास ऑप्टिमाइज़ेशन का उपयोग करके, कंटेनर के आकार को यथासंभव घटाने के लिए किया गया है।

4.EBO का उपयोग करके, स्वयं एक सरल मेमोरी एलोकेशन तथा रिलीज़ कार्यान्वित करना

सबसे पहले एक ऐसी क्लास परिभाषित करें जिसका sizeof(class)=1 हो; STL की तरह ही, इसमें allocate तथा deallocate से मेमोरी प्रबंधन किया जाता है।

class MyAllocator {
public:
    void *allocate(std::size_t size) {
        return std::malloc(size);
    }

    void deallocate(void *ptr) {
        std::free(ptr);
    }
};

पहले तरीके का मेमोरी प्रबंधन: एक मेमोरी प्रबंधन क्लास एम्बेड करना

template<class T, class Allocator>
class MyContainerNotEBO {
    T *data_ = nullptr;
    std::size_t capacity_;
    Allocator allocator_;   // 嵌入一个MyAllocator
public:
    MyContainerNotEBO(std::size_t capacity)
            : capacity_(capacity), allocator_(), data_(nullptr) {
        std::cout << "alloc malloc" << std::endl;
        data_ = reinterpret_cast<T *>(allocator_.allocate(capacity * sizeof(T))); // 分配内存
    }

    ~MyContainerNotEBO() {
        std::cout << "MyContainerNotEBO free malloc" << std::endl;
        allocator_.deallocate(data_);
    }
};

दूसरा तरीका: खाली बेस क्लास ऑप्टिमाइज़ेशन अपनाकर, इनहेरिटेंस से मेमोरी प्रबंधन कार्यक्षमता प्राप्त करना

template<class T, class Allocator>
class MyContainerEBO
        : public Allocator {    // 继承一个EBO
    T *data_ = nullptr;
    std::size_t capacity_;
public:
    MyContainerEBO(std::size_t capacity)
            : capacity_(capacity), data_(nullptr) {
        std::cout << "alloc malloc" << std::endl;
        data_ = reinterpret_cast<T *>(this->allocate(capacity * sizeof(T)));
    }

    ~MyContainerEBO() {
        std::cout << "MyContainerEBO free malloc" << std::endl;
        this->deallocate(data_);
    }
};

परीक्षण शुरू करते हैं:

int main() {
    MyContainerNotEBO<int, MyAllocator> notEbo = MyContainerNotEBO<int, MyAllocator>(0);
    std::cout << "Using Not EBO Test sizeof is " << sizeof(notEbo) << std::endl;
    MyContainerEBO<int, MyAllocator> ebo = MyContainerEBO<int, MyAllocator>(0);
    std::cout << "Using EBO Test sizeof is " << sizeof(ebo) << std::endl;

    return 0;
}

परीक्षण परिणाम:

alloc malloc
Using Not EBO Test sizeof is 24
alloc malloc
Using EBO Test sizeof is 16
MyContainerEBO free malloc
MyContainerNotEBO free malloc

हमने पाया कि EBO आधारित डिज़ाइन वास्तव में एम्बेडेड डिज़ाइन से कहीं बेहतर है। इसी के साथ यह अनुभाग पूरा हुआ।

संबंधित विषय

टिप्पणियाँ