On Thursday, 14 August 2014 at 17:16:42 UTC, Philippe Sigaud wrote:
From time to time, I try to speed up some array-heavy code by using std.array.Appender, reserving some capacity and so on.

It never works. Never. It gives me executables that are maybe 30-50% slower than bog-standard array code.

I don't do anything fancy: I just replace the types, use clear() instead of = null...

Do people here get good results from Appender? And if yes, how are you using it?

Quick test...

----------------
import std.array;
import std.datetime;
import std.stdio;

enum size = 1000;

void test1()
{
    auto arr = appender!(int[])();
    foreach(n; 0 .. size)
        arr.put(n);
}

void test2()
{
    int[] arr;
    foreach(n; 0 .. size)
        arr ~= n;
}

void test3()
{
    auto arr = new int[](size);
    foreach(n; 0 .. size)
        arr[n] = n;
}

void test4()
{
    auto arr = uninitializedArray!(int[])(size);
    foreach(n; 0 .. size)
        arr[n] = n;
}

void main()
{
    auto result = benchmark!(test1, test2, test3, test4)(10_000);
    writeln(cast(Duration)result[0]);
    writeln(cast(Duration)result[1]);
    writeln(cast(Duration)result[2]);
    writeln(cast(Duration)result[3]);
}

----------------

With size being 1000, I get

178 ms, 229 μs, and 4 hnsecs
321 ms, 272 μs, and 8 hnsecs
27 ms, 138 μs, and 7 hnsecs
24 ms, 970 μs, and 9 hnsecs

With size being 100,000, I get

15 secs, 731 ms, 499 μs, and 1 hnsec
29 secs, 339 ms, 553 μs, and 8 hnsecs
2 secs, 602 ms, 385 μs, and 2 hnsecs
2 secs, 409 ms, 448 μs, and 9 hnsecs

So, it looks like using Appender to create an array of ints is about twice as fast as appending to the array directly, and unsurprisingly, creating the array at the correct size up front and filling in the values is far faster than either, whether the array's elements are default-initialized or not. And the numbers are about the same if it's an array of char rather than an array of int.

Also, curiously, if I add a test which is the same as test2 (so it's just appending to the array) except that it calls reserve on the array with size, the result is almost the same as not reserving. It's a smidgeon faster but probably not enough to matter. So, it looks like reserve doesn't buy you much for some reason. Maybe the extra work for checking whether it needs to reallocate or whatever fancy logic it has to do in ~= dwarfs the extra cost of the reallocations? That certainly seems odd to me, but bizarrely, the evidence does seem to say that reserving doesn't help much. That should probably be looked into.

In any case, from what I can see, if all you're doing is creating an array and then throwing away the Appender, it's faster to use Appender than to directly append to the array. Maybe that changes with structs? I don't know. It would be interesting to know what's different about your code that's causing Appender to be slower, but from what I can see, it's definitely faster to use Appender unless you know the target size of the array up front.

- Jonathan M Davis

Reply via email to