vector, but without the overhead of dynamic memory allocation

  • Follow


I'm currently writing a program that deals with containers which
contain containers which contain containers.

I'm simplifying things here a bit, but what I have is something like:

class Port_Data {};

class IP_Data {

    vector<Port_Data> ports;
};

class MAC_Data {

    vector<IP_Data> ips;
};

class SnifferDatabase {

    vector<MAC_Data> macs;
};

SnifferDatabase *const g_sdb = new SnifferDatabase();

The program works but it's way too slow. What's slowing things down is
the vector class's use of "malloc/new" to allocate memory (this can be
improved slightly by calling reserve() on the vectors beforehand).

Now I don't know if I'm inventing the wheel here or not, but I've put
something together that fixes my performance problem.

Basically, I've created an Adapter class that lets you use a normal
array just as if it were a vector (it cuts out the dynamic memory
allocation). I get the feeling this has been done before??? My code
now looks like:

class Port_Data {};

class IP_Data {

    SuperArray<Port_Data,255> ports;
};

class MAC_Data {

    SuperArray<IP_Data,255> ips;
};

class SnifferDatabase {

    SuperArray<MAC_Data,255> macs;
};

SnifferDatabase *const g_sdb = new SnifferDatabase();

Now, as you can see, there's only one case of dynamic memory
allocation (i.e. when the g_sdb variable gets initialised).

My particular Adapter class is different from an std::vector in a few
ways:
* It can't be copy-constructed or assigned to (this is purely for my
own needs in my own program).
* The "push_back" method can take an argument of any type (again, this
is for my own program).

Anyway here's my code for SuperArray:

#include <cstddef>

template< typename T, std::size_t len >
class SuperArray {

private:

    /* ------ Prevent copy-construction and assignment------ */
    SuperArray(SuperArray const &);

    SuperArray &operator=(SuperArray const &);
    /* ----------------------------------------------------- */

    char unsigned raw_mem[ sizeof( T[len] ) ];

    T *const pbegin;

    T *pend;

    T const *const pend_of_allocated_space;

public:

    /* ---------- First here comes the types ---------- */
    typedef std::size_t size_type;

    typedef T value_type;

    typedef T &reference;
    typedef T const &const_reference;

    typedef T *iterator, *pointer;
    typedef T const *const_iterator, *const_pointer;
    /* ------------------------------------------------ */

    SuperArray()
       : pbegin( static_cast<T*>( static_cast<void*>(raw_mem) ) ),
         pend( pbegin ),
         pend_of_allocated_space( pbegin + len )
    {
        /* Nothing */
    }

    bool empty() const { return pend == pbegin; }

    size_type capacity() const { return len; }

    size_type max_size() const { return len; }

    size_type size() const { return pend - pbegin; }

    reference operator[](size_t const i) { return pbegin[i]; }
    const_reference operator[](size_t const i) const { return
pbegin[i]; }

    iterator begin() { return pbegin; }
    const_iterator begin() const { return pbegin; }

    iterator end() { return pend; }
    const_iterator end() const { return pend; }

    reference front() { return *pbegin; }
    const_reference front() const { return *pbegin; }

    reference back() { return pend[-1]; }
    const_reference back() const { return pend[-1]; }

    template<class X>
    void push_back(X const &val)
    {
        if ( pend_of_allocated_space == pend )
        {
            /* Wups we're full! */
            throw -1;
        }

        /* Let the following throw if it wants to */
        ::new(pend) T(val);

        ++pend;  /* This doesn't happens if the placement new throws
*/
    }

    void pop_back()
    {
        back().~T();

        --pend;
    }

    void clear()
    {
        while ( !empty() )
            pop_back();
    }

};

Of course I could add stuff to it like rbegin() and rend() but I don't
need that stuff in my program right now.
0
Reply virtual (44) 3/9/2011 3:25:57 AM

On 2011-3-9 11:25, Virchanza wrote:
>
> I'm currently writing a program that deals with containers which
> contain containers which contain containers.
>
> I'm simplifying things here a bit, but what I have is something like:

You may check Array and Intrusive containers in boost library.
They may do what you need.

http://www.boost.org/doc/libs/1_46_0/
0
Reply no69 (367) 3/9/2011 3:34:22 AM


On 3/8/2011 7:34 PM, Qi wrote:
> On 2011-3-9 11:25, Virchanza wrote:
>>
>> I'm currently writing a program that deals with containers which
>> contain containers which contain containers.
>>
>> I'm simplifying things here a bit, but what I have is something like:
>
> You may check Array and Intrusive containers in boost library.
> They may do what you need.
>
> http://www.boost.org/doc/libs/1_46_0/

Or std::tr1::array (C++03/TR1) or std::array (C++0x)

0
Reply no.spam.here (137) 3/9/2011 5:43:49 AM

On Mar 9, 4:25=A0am, Virchanza <virt...@lavabit.com> wrote:
> I'm currently writing a program that deals with containers which
> contain containers which contain containers.
>
> I'm simplifying things here a bit, but what I have is something like:
>
> class Port_Data {};
>
> class IP_Data {
>
> =A0 =A0 vector<Port_Data> ports;
>
> };
>
> class MAC_Data {
>
> =A0 =A0 vector<IP_Data> ips;
>
> };
>
> class SnifferDatabase {
>
> =A0 =A0 vector<MAC_Data> macs;
>
> };
>
> SnifferDatabase *const g_sdb =3D new SnifferDatabase();
>
> The program works but it's way too slow. What's slowing things down is
> the vector class's use of "malloc/new" to allocate memory (this can be
> improved slightly by calling reserve() on the vectors beforehand).

Once you've done a sufficiently big vector::reserve, there is no
additional allocation done by the vector. And yet, you say that this
improved things slightly. That is a clear-cut indication that
allocations done by the vector are not a problem (because there aren't
any!).

IOW... If you don't show your actual code, your performance results,
and your performance requirements, on an OPTIMIZED build, I don't
believe you, and you should not believe yourself when saying this,
either.

I put this to you: your problem is more likely to be in excessive
copying of things. (Re)allocation might be a problem, I am not denying
that in general, but I don't think that's your case, and I don't think
that you exhausted first rule of performance: eliminate busywork
(first target: random copying of stuff).

Goran.
0
Reply goran.pusic (299) 3/9/2011 9:00:58 AM

On 09/03/2011 03:25, Virchanza wrote:
>
> I'm currently writing a program that deals with containers which
> contain containers which contain containers.
>
> I'm simplifying things here a bit, but what I have is something like:
>
> class Port_Data {};
>
> class IP_Data {
>
>      vector<Port_Data>  ports;
> };
>
> class MAC_Data {
>
>      vector<IP_Data>  ips;
> };
>
> class SnifferDatabase {
>
>      vector<MAC_Data>  macs;
> };
>
> SnifferDatabase *const g_sdb = new SnifferDatabase();
>
> The program works but it's way too slow. What's slowing things down is
> the vector class's use of "malloc/new" to allocate memory (this can be
> improved slightly by calling reserve() on the vectors beforehand).
>
> Now I don't know if I'm inventing the wheel here or not, but I've put
> something together that fixes my performance problem.
>
> Basically, I've created an Adapter class that lets you use a normal
> array just as if it were a vector (it cuts out the dynamic memory
> allocation). I get the feeling this has been done before??? My code
> now looks like:
>
> class Port_Data {};
>
> class IP_Data {
>
>      SuperArray<Port_Data,255>  ports;
> };
>
> class MAC_Data {
>
>      SuperArray<IP_Data,255>  ips;
> };
>
> class SnifferDatabase {
>
>      SuperArray<MAC_Data,255>  macs;
> };
>
> SnifferDatabase *const g_sdb = new SnifferDatabase();
>
> Now, as you can see, there's only one case of dynamic memory
> allocation (i.e. when the g_sdb variable gets initialised).
>
> My particular Adapter class is different from an std::vector in a few
> ways:
> * It can't be copy-constructed or assigned to (this is purely for my
> own needs in my own program).
> * The "push_back" method can take an argument of any type (again, this
> is for my own program).
>
> Anyway here's my code for SuperArray:
>
> #include<cstddef>
>
> template<  typename T, std::size_t len>
> class SuperArray {
>
> private:
>
>      /* ------ Prevent copy-construction and assignment------ */
>      SuperArray(SuperArray const&);
>
>      SuperArray&operator=(SuperArray const&);
>      /* ----------------------------------------------------- */
>
>      char unsigned raw_mem[ sizeof( T[len] ) ];
>
>      T *const pbegin;
>
>      T *pend;
>
>      T const *const pend_of_allocated_space;
>
> public:
>
>      /* ---------- First here comes the types ---------- */
>      typedef std::size_t size_type;
>
>      typedef T value_type;
>
>      typedef T&reference;
>      typedef T const&const_reference;
>
>      typedef T *iterator, *pointer;
>      typedef T const *const_iterator, *const_pointer;
>      /* ------------------------------------------------ */
>
>      SuperArray()
>         : pbegin( static_cast<T*>( static_cast<void*>(raw_mem) ) ),
>           pend( pbegin ),
>           pend_of_allocated_space( pbegin + len )
>      {
>          /* Nothing */
>      }
>
>      bool empty() const { return pend == pbegin; }
>
>      size_type capacity() const { return len; }
>
>      size_type max_size() const { return len; }
>
>      size_type size() const { return pend - pbegin; }
>
>      reference operator[](size_t const i) { return pbegin[i]; }
>      const_reference operator[](size_t const i) const { return
> pbegin[i]; }
>
>      iterator begin() { return pbegin; }
>      const_iterator begin() const { return pbegin; }
>
>      iterator end() { return pend; }
>      const_iterator end() const { return pend; }
>
>      reference front() { return *pbegin; }
>      const_reference front() const { return *pbegin; }
>
>      reference back() { return pend[-1]; }
>      const_reference back() const { return pend[-1]; }
>
>      template<class X>
>      void push_back(X const&val)
>      {
>          if ( pend_of_allocated_space == pend )
>          {
>              /* Wups we're full! */
>              throw -1;
>          }
>
>          /* Let the following throw if it wants to */
>          ::new(pend) T(val);
>
>          ++pend;  /* This doesn't happens if the placement new throws
> */
>      }
>
>      void pop_back()
>      {
>          back().~T();
>
>          --pend;
>      }
>
>      void clear()
>      {
>          while ( !empty() )
>              pop_back();
>      }
>
> };
>
> Of course I could add stuff to it like rbegin() and rend() but I don't
> need that stuff in my program right now.

Your class would likely not work if placed on the stack due to alignment 
problems; check out the "vecarray" container I wrote instead:

http://i42.co.uk/stuff/vecarray.htm

It is open source and should satisfy your requirements.

/Leigh
0
Reply leigh (1003) 3/9/2011 10:53:16 AM

Goran <goran.pusic@gmail.com> wrote:
> Once you've done a sufficiently big vector::reserve, there is no
> additional allocation done by the vector. And yet, you say that this
> improved things slightly. That is a clear-cut indication that
> allocations done by the vector are not a problem (because there aren't
> any!).

  That would be incorrect: Of course std::vector is going to make at least
one allocation (eg. when you call reserve()).

  If I understood correctly, the original poster's wrapper around a
static array is probably something like this:

template<typename Value_t, unsigned kCapacity>
class StaticArrayWrapper
{
    Value_t valueArray[kCapacity];

 public:
    // Public member functions similar to the ones in std::vector
};

  Notice that unlike std::vector, the above class makes no memory
allocations at all. This makes a huge difference when you instantiate
it many times. For example:

class A
{
    std::vector<SomeType> data;

 public:
    A() { data.reserve(123); } // Makes an allocation
};

class B
{
    StaticArrayWrapper<SomeType, 123> data; // Makes no allocations
};

std::vector<A> aVector(1000); // Makes 1001 allocations in total
std::vector<B> bVector(1000); // Makes 1 allocation in total

  The difference is very significant.

0
Reply nospam270 (2853) 3/9/2011 11:09:35 AM

>"Goran" <goran.pusic@gmail.com> wrote in message 
>news:55bd9b44-8df2-4b34-8cc9-81b4d6f41296@r4g2000vbq.googlegroups.com...
>On Mar 9, 4:25 am, Virchanza <virt...@lavabit.com> wrote:
>> I'm currently writing a program that deals with containers which
>> contain containers which contain containers.
>>
>> I'm simplifying things here a bit, but what I have is something like:
>>
>> class Port_Data {};
>>
>> class IP_Data {
>>
>> vector<Port_Data> ports;
>>
>> };
>>>
>> class MAC_Data {
>>
>> vector<IP_Data> ips;
>>
>> };
>>
>> class SnifferDatabase {
>>
>> vector<MAC_Data> macs;
>>
>> };
>>
>> SnifferDatabase *const g_sdb = new SnifferDatabase();
>>
>> The program works but it's way too slow. What's slowing things down is
>> the vector class's use of "malloc/new" to allocate memory (this can be
>> improved slightly by calling reserve() on the vectors beforehand).

>Once you've done a sufficiently big vector::reserve, there is no
>additional allocation done by the vector. And yet, you say that this
>improved things slightly. That is a clear-cut indication that
>allocations done by the vector are not a problem (because there aren't
>any!).
>
>IOW... If you don't show your actual code, your performance results,
>and your performance requirements, on an OPTIMIZED build, I don't
>believe you, and you should not believe yourself when saying this,
>either.

>I put this to you: your problem is more likely to be in excessive
>copying of things. (Re)allocation might be a problem, I am not denying
>that in general, but I don't think that's your case, and I don't think
>that you exhausted first rule of performance: eliminate busywork
>(first target: random copying of stuff).

Yes the code needs to be copy optimised and also I think Juha has made a 
good point.

What is seems to need is ownership of the array/vector in the SnifferDB , I 
think in pointers so I use dynamics arrays(ofc you could have pointers to 
vectors):

class port_data{};

class IP_data{
port_data* p_port;
virtual void reserve(int val, port_data* p_data){};
public:
void reserve(int val){reserve(val, p_port);};
};

class MAC_data{
IP_data* p_ip;
virtual void reserve(int val, IP_data* p_data){};
public:
void reserve(int val){reserve(val, p_ip);}
};

class SnifferDB: public IP_data, public MAC_data{
MAC_data* p_mac;
void reserve(int val, IP_data* p_data){/*Allocate IP_data*/;}
void reserve(int val, port_data* p_data){/*Allocate port_data*/;}
};

So in the end you have 1 polymorphic object with 3 pointers, and easy access 
to each array/vectors.

If you want to pass the data around just pass a pointer and no copying is 
needed. Depending on program design it may require the  flexiblity of a 
common Interfase for IP_Data, MAC_data and port_data. This would then be a 
diamond inheretance design though and this may introduce some complexity 
with the virtual functions.


0
Reply pchristor (950) 3/9/2011 10:47:45 PM

6 Replies
25 Views

(page loaded in 1.081 seconds)


Reply: