Sunday, December 10, 2023

Move constructor and emplace_back for std::vector performance improvement (Native C++)

Performance improvement with move constructor and emplace_back against copy constructor and push_back is very visible: See below program output.

There are many pitfalls in move constructors. Please read the textbook before utilizing it.

BTW, this is just demonstration purpose for the pointer member to contain only one int value. In this particular case, replacing int *p with int  v improves the performance much more,

C++ Program

#include <iostream>
#include <vector>
#include <stdio.h>
#include <map>
using namespace std;

static int PtrToId(const void* p) {
    static int id = 400;
    static map<const void*, int> ptrIdMap;
    auto found = ptrIdMap.find(p);
    if (found != ptrIdMap.end()) {
        return found->second;
    }
    
    ptrIdMap[p] = id;
    id += 8;

    return ptrIdMap[p];
}

class A {
public:
    int *p = nullptr;
    static int newCounter;

    A(void) {
        p = new int;
        ++newCounter;
        *p = 0;
        cout << "Ctor  new this=" << PtrToId(this) << ", p=" << PtrToId(p) << endl;
    }

    A(int d) {
        p = new int;
        ++newCounter;
        *p = d;
        cout << "Ctor(int) this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << endl;
    }

    A(const A& rhs) {
        p = new int;
        ++newCounter;
        *p = *(rhs.p);
        cout << "Copy ctor this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << endl;
    }

    ~A(void) {
        cout << "Dtor  del this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << endl;
        delete p;
        p = nullptr;
    }
};
int A::newCounter = 0;

class B {
public:
    int *p = nullptr;
    static int newCounter;

    B(void) {
        p = new int;
        ++newCounter;
        *p = 0;
        cout << "Ctor  new this=" << PtrToId(this) << ", p=" << PtrToId(p) << endl;
    }

    B(int d) {
        p = new int;
        ++newCounter;
        *p = d;
        cout << "Ctor(int) this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << endl;
    };

    B(const B& rhs) {
        p = new int;
        ++newCounter;
        *p = *(rhs.p);
        cout << "Copy ctor this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << ", &rhs=" << PtrToId(&rhs) << endl;
    }

    B(B&& rhs) noexcept {
        p = rhs.p;
        rhs.p = nullptr;

        cout << "Move ctor this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << ", &rhs=" << PtrToId(&rhs) << endl;
    }

    ~B(void) {
        if (p) {
            cout << "Dtor  del this=" << PtrToId(this) << ", p=" << PtrToId(p) << ", *p=" << *p << endl;
            delete p;
            p = nullptr;
        } else {
            cout << "Dtor      this=" << PtrToId(this) << ", p=nullptr" << endl;
        }
    }
};
int B::newCounter = 0;

template <typename T>
static void Print(vector<T> &v, const char* name)
{
    for (int i = 0; i < v.size(); ++i) {
        printf(" &(%s[%d])=%d, p=%d, *p=%d\n", name, i, PtrToId(&(v[i])), PtrToId(v[i].p), *(v[i].p));
    }
}

int main(void)
{
    {
        printf("vA/push_back Begin\n");
        vector<A> vA;

        printf("push_back(10)\n");
        vA.push_back(10);
        Print(vA, "vA");

        printf("\npush_back(20)\n");
        vA.push_back(20);
        Print(vA, "vA");

        printf("\npush_back(30)\n");
        vA.push_back(30);
        Print(vA, "vA");

        printf("\nvA/push_back end\n");
    }
    printf("new int is called %d times.\n", A::newCounter);
    A::newCounter = 0;

    {
        printf("\nvA/emplace_back Begin\n");
        vector<A> vA;

        printf("emplace_back(10)\n");
        vA.emplace_back(10);
        Print(vA, "vA");

        printf("\nemplace_back(20)\n");
        vA.emplace_back(20);
        Print(vA, "vA");

        printf("\nemplace_back(30)\n");
        vA.emplace_back(30);
        Print(vA, "vA");

        printf("\nvA/emplace_back end\n");
    }
    printf("new int is called %d times.\n", A::newCounter);

    {
        printf("\nvB/push_back begin\n");
        vector<B> vB;

        printf("push_back(10)\n");
        vB.push_back(10);
        Print(vB, "vB");

        printf("\npush_back(20)\n");
        vB.push_back(20);
        Print(vB, "vB");

        printf("\npush_back(30)\n");
        vB.push_back(30);
        Print(vB, "vB");

        printf("\nvB/push_back end\n");
    }
    printf("new int is called %d times.\n", B::newCounter);

    B::newCounter = 0;
    {
        printf("\nvB/emplace_back begin\n");
        vector<B> vB;

        printf("emplace_back(10)\n");
        vB.emplace_back(10);
        Print(vB, "vB");

        printf("\emplace_back(20)\n");
        vB.emplace_back(20);
        Print(vB, "vB");

        printf("\emplace_back(30)\n");
        vB.emplace_back(30);
        Print(vB, "vB");

        printf("\nvB/emplace_back end\n");
    }
    printf("new int is called %d times.\n", B::newCounter);

    return 0;
}





 

Program Output

vA/push_back Begin
push_back(10)
Ctor(int) this=408, p=400, *p=10
Copy ctor this=424, p=416, *p=10
Dtor  del this=408, p=400, *p=10
 &(vA[0])=424, p=416, *p=10

push_back(20)
Ctor(int) this=440, p=432, *p=20
Copy ctor this=456, p=448, *p=20
Copy ctor this=472, p=464, *p=10
Dtor  del this=424, p=416, *p=10
Dtor  del this=440, p=432, *p=20
 &(vA[0])=472, p=464, *p=10
 &(vA[1])=456, p=448, *p=20

push_back(30)
Ctor(int) this=488, p=480, *p=30
Copy ctor this=496, p=416, *p=30
Copy ctor this=512, p=504, *p=10
Copy ctor this=528, p=520, *p=20
Dtor  del this=472, p=464, *p=10
Dtor  del this=456, p=448, *p=20
Dtor  del this=488, p=480, *p=30
 &(vA[0])=512, p=504, *p=10
 &(vA[1])=528, p=520, *p=20
 &(vA[2])=496, p=416, *p=30

vA/push_back end
Dtor  del this=512, p=504, *p=10
Dtor  del this=528, p=520, *p=20
Dtor  del this=496, p=416, *p=30
new int is called 9 times.

vA/emplace_back Begin
emplace_back(10)
Ctor(int) this=472, p=480, *p=10
 &(vA[0])=472, p=480, *p=10

emplace_back(20)
Ctor(int) this=544, p=536, *p=20
Copy ctor this=560, p=552, *p=10
Dtor  del this=472, p=480, *p=10
 &(vA[0])=560, p=552, *p=10
 &(vA[1])=544, p=536, *p=20

emplace_back(30)
Ctor(int) this=576, p=568, *p=30
Copy ctor this=592, p=584, *p=10
Copy ctor this=608, p=600, *p=20
Dtor  del this=560, p=552, *p=10
Dtor  del this=544, p=536, *p=20
 &(vA[0])=592, p=584, *p=10
 &(vA[1])=608, p=600, *p=20
 &(vA[2])=576, p=568, *p=30

vA/emplace_back end
Dtor  del this=592, p=584, *p=10
Dtor  del this=608, p=600, *p=20
Dtor  del this=576, p=568, *p=30
new int is called 6 times.

vB/push_back begin
push_back(10)
Ctor(int) this=624, p=616, *p=10
Move ctor this=632, p=616, *p=10, &rhs=624
Dtor      this=624, p=nullptr
 &(vB[0])=632, p=616, *p=10

push_back(20)
Ctor(int) this=648, p=640, *p=20
Move ctor this=656, p=640, *p=20, &rhs=648
Move ctor this=664, p=616, *p=10, &rhs=632
Dtor      this=632, p=nullptr
Dtor      this=648, p=nullptr
 &(vB[0])=664, p=616, *p=10
 &(vB[1])=656, p=640, *p=20

push_back(30)
Ctor(int) this=680, p=672, *p=30
Move ctor this=688, p=672, *p=30, &rhs=680
Move ctor this=696, p=616, *p=10, &rhs=664
Move ctor this=704, p=640, *p=20, &rhs=656
Dtor      this=664, p=nullptr
Dtor      this=656, p=nullptr
Dtor      this=680, p=nullptr
 &(vB[0])=696, p=616, *p=10
 &(vB[1])=704, p=640, *p=20
 &(vB[2])=688, p=672, *p=30

vB/push_back end
Dtor  del this=696, p=616, *p=10
Dtor  del this=704, p=640, *p=20
Dtor  del this=688, p=672, *p=30
new int is called 3 times.

vB/emplace_back begin
emplace_back(10)
Ctor(int) this=712, p=552, *p=10
 &(vB[0])=712, p=552, *p=10
emplace_back(20)
Ctor(int) this=728, p=720, *p=20
Move ctor this=632, p=552, *p=10, &rhs=712
Dtor      this=712, p=nullptr
 &(vB[0])=632, p=552, *p=10
 &(vB[1])=728, p=720, *p=20
emplace_back(30)
Ctor(int) this=576, p=448, *p=30
Move ctor this=592, p=552, *p=10, &rhs=632
Move ctor this=608, p=720, *p=20, &rhs=728
Dtor      this=632, p=nullptr
Dtor      this=728, p=nullptr
 &(vB[0])=592, p=552, *p=10
 &(vB[1])=608, p=720, *p=20
 &(vB[2])=576, p=448, *p=30

vB/emplace_back end
Dtor  del this=592, p=552, *p=10
Dtor  del this=608, p=720, *p=20
Dtor  del this=576, p=448, *p=30
new int is called 3 times.

No comments:

Post a Comment