HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Custom vector that uses less memory than std::vector

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
stdthanusescustomlessthatmemoryvector

Problem

I'm using vectors to create matrix and the vector itself is consuming a large space. So I implemented a version that would use less memory and, if possible, be as fast or faster than std::vector.

How can I improve the performance and is there anything that should be changed?

```
#ifndef __DATAARRAY_H__
#define __DATAARRAY_H__
#include
#include "core/Exceptions/ArgumentException.h"

template class Sizer { };
namespace Core
{
#pragma pack(1)
template
class DataArray
{
public:
DataArray() :m_iDataSize(0), m_aData(nullptr), m_iDataCapacity(0){
}
DataArray(MaxSizeType size) :m_iDataSize(0){
_Reserve(size);
}

DataArray(const DataArray& dArray)
{
_Reserve(dArray.m_iDataCapacity);
_CopyData(dArray);
}
void reserve(MaxSizeType size){
}

void resize(MaxSizeType size){
if (size = size())
throw std::invalid_argument("Not enought memory");
return m_aData[id];
}

DataType& operator=(const DataArray& dArray)
{
if (this == &dArray)
return *this;
_Delete_m_aData();
_Reserve(dArray.m_iDataCapacity);
_CopyData(dArray);
return *this;
}

MaxSizeType size(){
return m_iDataSize;
}

MaxSizeType Capacity(){
return m_iDataCapacity;
}

~DataArray(){
_Delete_m_aData();
}
protected:
inline void _Reserve(MaxSizeType size){
if (size > m_iDataCapacity){
DataType new_Data = (DataType)std::realloc(m_aData, sizeof(DataType)*size);
if (new_Data != NULL){
m_aData = new_Data;

Solution

Size Requirements

You talk about wanting to "use less memory" than std::vector. But std::vector doesn't use that much memory. It's three pointers. I guess if you set your MaxSizeType to int instead of size_t, you can have a vector of 16 bytes instead of 24, but I would really have to see what you're doing that that difference should matter.

Performance

There's two glaring issues that jump out at me as far as performance goes. I suspect this would be substantially slower than std::vector.

First one is on push_back:

void push_back(DataType element){
    if (m_iDataCapacity <= m_iDataSize)
    {
        _Reserve(m_iDataCapacity + 1);
    }
    new (m_aData + m_iDataSize)DataType(element);
    m_iDataSize++;
}


First, you're taking your DataType by value. That's incurring an extra copy, which you don't want to do. Secondly, your resize policy is increasing capacity by one. This means that your push_back() is expected \$O(n)\$. There's an easy explanation for this too: once we reach our capacity, we will have to _Reserve() on every subsequent push_back(), which means we're copying \$n\$ elements every time. The right thing to do is to increase capacity by a constant factor.

The next issue comes from indexing:

DataType& operator[](MaxSizeType id)
{
    if (id = size()) 
       throw std::invalid_argument("Not enought memory");
    return m_aData[id];
}


First, the exception is misleading. A better exception type would be std::out_of_range, and a message indicating this - since the issue isn't that we ran out of memory. That's an error that we would throw if allocation fails (in _Reserve, you do the same thing - there you should throw std::bad_alloc).

But the main problem is this kills performance because on every lookup, we have a branch. Every time. And branches are slow. That's why on std::vector, operator[] is defined simply as:

reference
operator[](size_type __n) 
{    
    return *(this->_M_impl._M_start + __n);
}


If you want bounds checking, you can provide a separate member function called at().

Other Issues

You're missing a lot of other nice features that std::vector provides:

  • Missing iterators, begin(), and end().



  • Missing const overloads of member functions



-
This cast is unnecessary, since pData is already a DataType.

((DataType*)pData)->~DataType();

Code Snippets

void push_back(DataType element){
    if (m_iDataCapacity <= m_iDataSize)
    {
        _Reserve(m_iDataCapacity + 1);
    }
    new (m_aData + m_iDataSize)DataType(element);
    m_iDataSize++;
}
DataType& operator[](MaxSizeType id)
{
    if (id < 0 || id >= size()) 
       throw std::invalid_argument("Not enought memory");
    return m_aData[id];
}
reference
operator[](size_type __n) 
{    
    return *(this->_M_impl._M_start + __n);
}
((DataType*)pData)->~DataType();

Context

StackExchange Code Review Q#111114, answer score: 10

Revisions (0)

No revisions yet.