How to avoid successive deallocations/allocations in C++?

前端 未结 10 814
失恋的感觉
失恋的感觉 2021-02-07 18:26

Consider the following code:

class A
{
    B* b; // an A object owns a B object

    A() : b(NULL) { } // we don\'t know what b will be when constructing A

             


        
相关标签:
10条回答
  • 2021-02-07 18:53

    A quick test of Martin York's assertion that this is a premature optimisation, and that new/delete are optimised well beyond the ability of mere programmers to improve. Obviously the questioner will have to time his own code to see whether avoiding new/delete helps him, but it seems to me that for certain classes and uses it will make a big difference:

    #include <iostream>
    #include <vector>
    
    int g_construct = 0;
    int g_destruct = 0;
    
    struct A {
        std::vector<int> vec;
        A (int a, int b) : vec((a*b) % 2) { ++g_construct; }
        ~A() { 
            ++g_destruct; 
        }
    };
    
    int main() {
        const int times = 10*1000*1000;
        #if DYNAMIC
            std::cout << "dynamic\n";
            A *x = new A(1,3);
            for (int i = 0; i < times; ++i) {
                delete x;
                x = new A(i,3);
            }
        #else
            std::cout << "automatic\n";
            char x[sizeof(A)];
            A* yzz = new (x) A(1,3);
            for (int i = 0; i < times; ++i) {
                yzz->~A();
                new (x) A(i,3);
            }
        #endif
    
        std::cout << g_construct << " constructors and " << g_destruct << " destructors\n";
    }
    
    $ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
    automatic
    10000001 constructors and 10000000 destructors
    
    real    0m7.718s
    user    0m7.671s
    sys     0m0.030s
    
    $ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
    dynamic
    10000001 constructors and 10000000 destructors
    
    real    0m15.188s
    user    0m15.077s
    sys     0m0.047s
    

    This is roughly what I expected: the GMan-style (destruct/placement new) code takes twice as long, and is presumably doing twice as much allocation. If the vector member of A is replaced with an int, then the GMan-style code takes a fraction of a second. That's GCC 3.

    $ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
    dynamic
    10000001 constructors and 10000000 destructors
    
    real    0m5.969s
    user    0m5.905s
    sys     0m0.030s
    
    $ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
    automatic
    10000001 constructors and 10000000 destructors
    
    real    0m2.047s
    user    0m1.983s
    sys     0m0.000s
    

    This I'm not so sure about, though: now the delete/new takes three times as long as the destruct/placement new version.

    [Edit: I think I've figured it out - GCC 4 is faster on the 0-sized vectors, in effect subtracting a constant time from both versions of the code. Changing (a*b)%2 to (a*b)%2+1 restores the 2:1 time ratio, with 3.7s vs 7.5]

    Note that I've not taken any special steps to correctly align the stack array, but printing the address shows it's 16-aligned.

    Also, -g doesn't affect the timings. I left it in accidentally after I was looking at the objdump to check that -O3 hadn't completely removed the loop. That pointers called yzz because searching for "y" didn't go quite as well as I'd hoped. But I've just re-run without it.

    0 讨论(0)
  • 2021-02-07 18:54

    Simply reserve the memory required for b (via a pool or by hand) and reuse it each time you delete/new instead of reallocating each time.

    Example :

    class A
    {
        B* b; // an A object owns a B object
        bool initialized;
    public:
        A() : b( malloc( sizeof(B) ) ), initialized(false) { } // We reserve memory for b
        ~A() { if(initialized) destroy(); free(b); } // release memory only once we don't use it anymore
    
        void calledVeryOften(…)
        {
            if (initialized)
                destroy();
    
            create();
        }
    
     private:
    
        void destroy() { b->~B(); initialized = false; } // hand call to the destructor
        void create( param1, param2, param3, param4 )
        {
            b = new (b) B( param1, param2, param3, param4 ); // in place new : only construct, don't allocate but use the memory that the provided pointer point to
            initialized = true;
        }
    
    };
    

    In some cases a Pool or ObjectPool could be a better implementation of the same idea.

    The construction/destruction cost will then only be dependante on the constructor and destructor of the B class.

    0 讨论(0)
  • 2021-02-07 18:54

    Just have a pile of previously used Bs, and re-use them.

    0 讨论(0)
  • 2021-02-07 19:05

    I'd go with boost::scoped_ptr here:

    class A: boost::noncopyable
    {
        typedef boost::scoped_ptr<B> b_ptr;
        b_ptr pb_;
    
    public:
    
        A() : pb_() {}
    
        void calledVeryOften( /*…*/ )
        {
            pb_.reset( new B( params )); // old instance deallocated
            // safely use *pb_ as reference to instance of B
        }
    };
    

    No need for hand-crafted destructor, A is non-copyable, as it should be in your original code, not to leak memory on copy/assignment.

    I'd suggest to re-think the design though if you need to re-allocate some inner state object very often. Look into Flyweight and State patterns.

    0 讨论(0)
  • 2021-02-07 19:08

    Like the others have already suggested: Try placement new..

    Here is a complete example:

    #include <new>
    #include <stdio.h>
    
    class B
    {
      public:
      int dummy;
    
      B (int arg)
      {
        dummy = arg;
        printf ("C'Tor called\n");
      }
    
      ~B ()
      {
        printf ("D'tor called\n");
      }
    };
    
    
    void called_often (B * arg)
    {
      // call D'tor without freeing memory:
      arg->~B();
    
      // call C'tor without allocating memory:
      arg = new(arg) B(10);
    }
    
    int main (int argc, char **args)
    {
      B test(1);
      called_often (&test);
    }
    
    0 讨论(0)
  • 2021-02-07 19:10

    How about allocating the memory for B once (or for it's biggest possible variant) and using placement new?

    A would store char memB[sizeof(BiggestB)]; and a B*. Sure, you'd need to manually call the destructors, but no memory would be allocated/deallocated.

       void* p = memB;
       B* b = new(p) SomeB();
       ...
       b->~B();   // explicit destructor call when needed.
    
    0 讨论(0)
提交回复
热议问题