1

In the code base I'm working on, it currently has code that does this often:

// In the header:
class Label
{
public:
    void ParseText();

private:
    Letter* m_myArray;
};

// In the CPP:
void ParseText()
{
    delete[] m_myArray;
    m_myArray = new Letter[string_length];
    // ......
}

Basically every time the string changes in the label, we delete the old set of letter objects and create them all over again. These letter objects are somewhat lightweight, but since this happens often I can't simply use std::vector<Letter> since each push_back() would result in a copy. I'd like to avoid the copy too.

Would using boost pool help here? I can imagine doing this (this is pseudocode, since I'm not sure how to use boost pool exactly yet):

// In the header:
class Label
{
public:
    void ParseText();

private:
    std::vector<Letter*> m_myArray;
    boost::object_pool m_pool;
};

// In the CPP:
void ParseText()
{
    // Loop through each element in m_myArray and call pool::free
    m_myArray.clear();

    // Loop each letter and create a new Letter object in the container
    for( ... ) {
        m_myArray.push_back(m_pool.malloc()); // Not sure how to handle constructor params
    }

    // ......
}

This would avoid the copy and would avoid doing allocations so often. However, I've lowered the maintainability of the code since there is so much boilerplate involved in adding/deleting items from the vector.

I've thought of using boost::ptr_vector with a custom deleter, but not sure if this helps much. It helps cleanup but I still have to call pool::malloc() each time I do a push_back.

Using a custom allocator with std::vector doesn't seem to make sense either since it's preallocated anyway and won't shrink in size.

Can anyone help me figure out the "best" solution for this problem?

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • 2
    Boost Pool is almost certainly not the tool I would use to address this problem. Instead I would focus on avoiding the copy when inserting into the vector. Making the object movable is one obvious way. Also obviously you should ensure you have called `reserve()` on the vector at the earliest possible time. – Alastair Apr 21 '14 at 14:38
  • 1
    Can we see the class definition for `Letter` too? – Mark B Apr 21 '14 at 14:58
  • For vector, first call reserve() to allocate all needed memory at once, then call emplace_back() to construct letter objects directly in that memory thus avoiding any copies (and move constructors). This, of course, assuming, you have this C++11 feature available. If you don't have C++11 vector with emplace_back(), you can use boost::container::vector<> which has emplace_back() even on older compilers. – Artem Tokmakov Apr 21 '14 at 15:52
  • @MarkB `Letter` is basically just a struct with 1 pointer and 3 floats. – void.pointer Apr 21 '14 at 16:13
  • why not just use `deque`? very similar to `vector` but doesn't need copy object in `push_back` – Bryan Chen Apr 23 '14 at 03:09
  • Current answers I have aren't acceptable ATM, but Bryan and Alastair if you'd be so kind as to put in answers for your great comments, I'd be happy to upvote or accept them. Too many questions on SO these days get great answers in comments when they should have been posted as actual answers! – void.pointer May 02 '14 at 13:48

2 Answers2

0

What I think I would do is use vector and resize to minimize the amount of allocations, and allow letters to be reused. So we have something like this:

// In the header:
class Label
{
public:
    void ParseText();

private:
    std::vector<Letter> m_myArray;
};

// In the CPP:
void ParseText()
{
    m_myArray.resize(string_length);
    // ......
}

With an approach like this, as many Letter objects as possible are reused from the previous instance. You can even call reserve in the Label constructor to pre-allocate enough space to even prevent any copy/movement of the Letter objects later.

Mark B
  • 95,107
  • 10
  • 109
  • 188
0

I think a memory pool would make a difference in some situations. Since boost::object_pool<> doesn't provide a method to allocate a array of objects, so I would use boost::pool<> which is actually the underlying memory pool of boost::object_pool<>.

#include <cstdio>
#include <ctime>
#include "boost/pool/pool.hpp"

struct Letter{
    float a, b, c;
    int *p;
};

class Label
{
public:
    Label() : m_myArray(NULL), string_length(1), last_size(0){}

    void set_size(size_t n)
    {
        last_size = string_length; // use last_size to store the number of last allocation, just for test.
        string_length = n;
    }
    void ParseText()
    {
        delete[] m_myArray;
        m_myArray = new Letter[string_length];
    }

    void ParseText_pool();

private:
    Letter* m_myArray;
    size_t string_length;
    size_t last_size; //boost::pool<>::ordered_free need the size
};

boost::pool<> p(sizeof(Letter));

void Label::ParseText_pool()
{
    if(m_myArray)
        p.ordered_free(m_myArray, last_size); // ordered_free need the right size
    m_myArray = (Letter*)p.ordered_malloc(string_length); // if you need call the ctor, use placement new.
}

int main()
{
    Label l;

    float startTime = (float)clock()/CLOCKS_PER_SEC;

    for(int i = 1; i < 1000000; ++i)
    {
        l.set_size(i%100 + 1);
        l.ParseText();
    }

    float endTime = (float)clock()/CLOCKS_PER_SEC;

    printf("without pool, time: %f\n", endTime - startTime);

    Label l2;
    startTime = (float)clock()/CLOCKS_PER_SEC;

    for(int i = 1; i < 1000000; ++i)
    {
        l.set_size(i%100 + 1);
        l2.ParseText_pool();
    }

    endTime = (float)clock()/CLOCKS_PER_SEC;

    printf("with pool, time: %f\n", endTime - startTime);
};

Run on my machine and coliru, it shows the more frequent the allocation, the greater the advantage of using a memory pool.

jfly
  • 7,715
  • 3
  • 35
  • 65