Efficiency of using array as stack
Ivan Kazmenko
gassa at mail.ru
Sat Mar 23 16:45:21 PDT 2013
On Saturday, 23 March 2013 at 19:33:06 UTC, Jonathan M Davis
wrote:
> You might want to check out this article where someone ran into
> similar issues:
>
> https://www.semitwist.com/articles/article/view/don-t-use-arrays-as-stacks
>
> And if you haven't read Steven's article on arrays, you should
> do so:
>
> http://dlang.org/d-array-article.html
That was a good reading, thank you! The whole matter became
clearer for me.
But now, I have one more example which confuses me, and the
articles don't seem to help right away with it. In this example,
I am moving a "sliding window" of length n; now that we're done
with the stack, here is a simple usage pattern of the queue.
What I do is repeatedly (n times also) add an element to the back
of the array and then remove an element from the front of it,
keeping the whole queue constantly large. Before the start, I
reserve c*n entries for my array, where c is a real number
between 1 and 2. I track reallocations by monitoring the address
of an element of a which should remain still as long as the array
is not reallocated.
-----
import std.algorithm;
import std.array;
import std.range;
import std.stdio;
void main ()
{
int n = 2_000_000;
double c = 1.5;
auto a = array (iota (n));
a.reserve (cast (int) (n * c));
auto prev = &a[$ - 1];
int changes = 0;
foreach (i; 0..n)
{
debug {writeln (a.capacity);}
a.length++;
a = a[1..$];
auto next = &a[$ - i - 1];
if (prev != next)
{
changes++;
prev = next;
}
}
writeln (changes);
}
-----
Now, if I set c to 2, there are no reallocations: all the work
goes with the 2*n pre-allocated elements. But as I decrease c
down to 1, the number of reallocations grows *linearly*, roughly
1 per 2000 appends. So for c = 1, there are 1044 reallocations
in total. And that's still quadratic behavior, albeit divided by
a large constant (~2000)!
What happens (locally) is, once the array size is not enough, it
starts being grown by blocks of 1024 or 891 (?!) elements in a
repeating pattern; one of these reallocates, the other does not.
The textbook growth implementation suggests doubling the array
size, but it looks like D attempts something smarter here.
However, in my case, the result is ~8x slower behavior when not
pre-allocating. The more is n, the slower is the version without
pre-allocation (c = 1) compared to the version with
pre-allocation (c = 2). And this means that a D array is not
also an efficient queue out-of-the-box. However, as in the case
with stack, it could perhaps be efficient with a bit of tweaking
(some custom tailored calls to reserve perhaps?).
So, what is the exact strategy of growing an array? Maybe you
could just kindly point me to some more interesting reading in
druntime source? And, well, now how do I implement a generally
efficient array-based queue in D?
-----
Ivan Kazmenko.
More information about the Digitalmars-d-learn
mailing list