I just liked the question and did some experiments myself, using MSVC14
compiler (optimizations disabled).
C++11/14 has the following sequence containers (intentionally excluded dynarry
introduced in C++14):
- No dynamic resizing (up to the programmer to allocate and deallocate)
- Raw array (e.g.
int char[]
)
- Array (e.g.
new array<int, size>(){...}
)
- With Dynamic resizing
- Vector (consecutive memory allocation)
- list (linked-list like array)
- forward_list (similar to list)
- deque (double ended queue)
Let me start with your questions,
the solution of allocating a new array with a size+1, and then copying
all the current elements into it and reading a new element, but I find
that this would be a brutal waste of processing time and memory
You are right, but to mitigate the overhead, when you allocate memory to use and then you figure out you need more memory than allocated, you need to allocate new memory and copy the previous data, then free the previous allocated memory.
But wait! How much to allocated (size+1 is bad)? Each time you are forced to allocate bigger chunk of memory, you better allocate twice the size you had already in hand so that you reduce the probability of another memory reallocation; because it is considered an extremely expensive operation.
if it is possible to simply allocate the next memory address and use
it for my array. Is there a way to do that in C++?
It's not totally in your control as C++ runtime has implemented memory management functions. Where your newly allocated memory will be, is not in your control, however sometimes it happens that the newly allocated space will have the same base address as the previous one; it depends on the runtime and the memory fragmentation it faces.
I got some benchmarks using malloc
and realloc
functions borrowed
from C. Here is the code:
auto start = chrono::steady_clock::now();
auto partialSize = 100;
auto arr = (int *) malloc(sizeof(int) * partialSize);
for (auto i = 0; i < SIZE; i++) {
arr[i] = i;
if (i == partialSize - 1) {
partialSize = partialSize << 1; // for 2X
arr = (int *) realloc(arr, sizeof(int) * partialSize);
}
}
auto duration = chrono::steady_clock::now() - start;
free(arr);
cout << "Duration: " << chrono::duration_cast<chrono::milliseconds>(duration).count() << "ms" << endl;
Results (for insertion of 100,000,000 integers; time is avg. of 3 runs):
- Start Size = 100, Increment Steps = 1.5X, Time(s) = 1.35s
- Start Size = 100, Increment Steps = 2X, Time(s) = 0.65s
Start Size = 100, Increment Steps = 4X, Time(s) = 0.42s
Start Size = 10,000, Increment Steps = 1.5X, Time(s) = 0.96s
- Start Size = 10,000, Increment Steps = 2X, Time(s) = 0.79s
Start Size = 10,000, Increment Steps = 4X, Time(s) = 0.51s
Another case is using C++'s new
keyword and checking for relocation:
auto start = chrono::steady_clock::now();
auto partialSize = 100;
auto arr = new int[partialSize];
for (auto i = 0; i < SIZE; i++) {
arr[i] = i;
if (i == partialSize - 1) {
auto newArr = new int[partialSize << 2]; // for 4X
partialSize = partialSize << 2;
arr = newArr;
}
}
auto duration = chrono::steady_clock::now() - start;
delete[] arr;
cout << "Duration: " << chrono::duration_cast<chrono::milliseconds>(duration).count() << "ms" << endl;
Results (for insertion of 100,000,000 integers; time is avg. of 3 runs):
- Start Size = 100, Increment Steps = 1.5X, Time(s) = 0.63s
- Start Size = 100, Increment Steps = 2X, Time(s) = 0.44s
Start Size = 100, Increment Steps = 4X, Time(s) = 0.36s
Start Size = 10,000, Increment Steps = 1.5X, Time(s) = 0.65s
- Start Size = 10,000, Increment Steps = 2X, Time(s) = 0.52s
- Start Size = 10,000, Increment Steps = 4X, Time(s) = 0.42s
For the rest (dynamic resizable containers):
auto start = chrono::steady_clock::now();
//auto arr = vector<int>{};
//auto arr = list<int>{};
//auto arr = new std::array<int, SIZE>{};
//auto arr = new int[SIZE];
//auto arr = deque<int>{};
auto arr = forward_list<int>{};
for (auto i = 0; i < SIZE; i++) {
arr.push_front(i);
// arr.push_back(i)
}
auto duration = chrono::steady_clock::now() - start;
cout << "Duration: " << chrono::duration_cast<chrono::milliseconds>(duration).count() << "ms" << endl;
Results (for insertion of 100,000,000 integers; time is avg. of 3 runs):
vector
list
array (no reallocation)
- Time(s) = N/A; Error: Compiler is out of heap.
raw int array (no reallocation)
deque
forward_list
Hope it helps.