Hi Codeforces!
Today while solving problem F from Zepto-cup I need an array of linked lists of size about 6·105 and I decided to use std::stack. But I got ML cause that array has a size of about 350MB. Then I replaced std::stack by std::list and got AC. Anyone know why std::stack use so lot of memory?
#include <stack>
#include <cstdlib>
#include <iostream>
using namespace std;
const int N = 600 * 1000 + 3;
stack<int> z[N];
int main()
{
z[rand()].push(rand());
cout << z[rand()].size() << endl;
return 0;
}
=====
Used: 420 ms, 352524 KB
#include <list>
#include <cstdlib>
#include <iostream>
using namespace std;
const int N = 600 * 1000 + 3;
list<int> z[N];
int main()
{
z[rand()].push_back(rand());
cout << z[rand()].size() << endl;
return 0;
}
=====
Used: 0 ms, 4704 KB
Cause of tricky deque structure. Deque is not a linked list in GCC right now: you can access to arbitrary element in O(1) time.
Good start to digg into deque implementation.
On my PC this code gives as output:
Deque is just a default container, we can replace it:
Output:
4
But why stack was not implemented simply as linked list?
If you use
std::list<>
to implementstd::stack<>
, then for each push/pop there is a heap allocation/deallocation, which is slow.If you use
std::vector<>
to implementstd::stack<>
, then the allocated memory might be significantly bigger than really used by the stack; also,std::vector<>
is required to store data contiguously, and allocating one big chunk of memory is more problematic than allocating many small chunks.Therefore we arrive to a container that is "in the middle" between
std::list<>
andstd::vector<>
, and is the default underlying container forstd::stack<>
:std::deque<>
.P.S. In competitive programming
std::stack<T, std::vector<T>>
is actually quite good most of the time.Actually I asked why stack wasn't implemented as separate container, why it should use another container to store data?
First, to reduce code duplication.
std::list<>
,std::vector<>
,std::deque<>
are all distinct data structures, and so are implemented separately. But implementingstd::stack<>
nearly means writing one of the above again. Instead of this it is better to just re-use the existing solution.Second, you can see that
std::stack<>
can be implemented in more than one way. The current interface allows to choose freely between implementations.Thanks for an explanation.
I've looked at the source of
std::deque<T>
implementation inlibstdc++
.The underlying structure of this type is not just an array of T, but an array of arrays of T. Each of these arrays of T is at least 512 bytes long.
It's obvious that the authors wanted to make the memory footprint be a constant value, instead of C * size(), but the solution appears to be awful.
If you want to get more details, you can read
<bits/stl_deque.h>
.std::deque<T>
needs to support constant-time element insertion and removal at the front. This places restrictions on the container implementation.