Asking about performance of std concatenation vs. Appender vs. custom class
ludo
fakeaddress at gmail.com
Thu Apr 1 14:53:20 UTC 2021
Hi fellow Dlangers,
I am working on an old codebase, and found a re-implementation of
standard dynamic arrays as a struct called [ArrayBuilder, visible
on my github](https://tinyurl.com/2nr25ytt)
The comment says:
```d
/**
* Behaves the same as built-in arrays, except about 6x faster
with concatenation at the expense of the base pointer being 4
system words instead of two (16 instead of 8 bytes on a 32-bit
system).
*/
```
So I wrote a unittest to verify that claim, using for benchmark
10_000_000 concatenations :
```d
trace("*** ArrayBuilder Concatenation benchmark ***");
import std.datetime.stopwatch : benchmark, Duration;
import std.array : appender;
void concat_withStd()
{
int[] array;
for (int j = 0; j < 1000; j++)
array ~= j;
}
void concat_withStdReserve()
{
int[] array;
array.reserve(1000);
for (int j = 0; j < 1000; j++)
array ~= j;
}
void concat_withStdLength()
{
int[] array;
array.length = 1000;
for (int j = 0; j < 1000; j++)
array[j] = j;
}
void concat_withAppenderReserve()
{
auto array = appender!(int[]);
//trace ("array is ", array);
array.reserve(1000);
for (int j = 0; j < 1000; j++)
array ~= j;
}
void concat_withArrayBuilder()
{
ArrayBuilder!(int) array;
for (int j = 0; j < 1000; j++)
array ~= j;
}
auto r = benchmark!(concat_withStd,
(...)//stuff
concat_withArrayBuilder)(10_000);
tracef("with std: %s", bench1);
tracef("with stdReserve: %s", bench2);
tracef("with stdLength: %s", bench3);
tracef("with AppenderReserve: %s", bench4);
tracef("with Arraybuilder: %s \n", bench5);
```
The results are below (you can also git clone the repo + dub
test):
| Concatenation method | benchmark in ms|
|---------------------|---------------------|
|with std: | 385 ms|
|with stdReserve: | 327 ms|
|with stdLength: | 29 ms|
|with AppenderReserve:| 256 ms|
|with Arraybuilder: | 118 ms|
- The use of reserve does not seem to improve much the standard
concatenation performance. **Would you know why?**
- Increasing the length of the array before-hand is by far the
best solution, as expected.
- AppenderReserve outperforms a basic concatenation, but the gain
is not completely obvious, even with a call to reserve. **Is it
expected behavior?**
- ArrayBuilder is 3 times faster than standard concatenation, and
twice as fast as AppenderReserve! **Which explanation would you
give?** I looked at the ArrayBuilder struct, it's a very basic
code with no magic trick. I also looked at the [phobos Appender
code on
github](https://github.com/dlang/phobos/blob/master/std/array.d),
in particular the code for put() line 3460, it's obviously more
complex:
```d
import core.internal.lifetime : emplaceRef;
ensureAddable(1);
immutable len = _data.arr.length;
auto bigData = (() @trusted => _data.arr.ptr[0 .. len
+ 1])();
emplaceRef!(Unqual!T)(bigData[len], cast() item);
//We do this at the end, in case of exceptions
_data.arr = bigData;
```
There are some functions calls... Maybe the drag comes from
there. I am really out of my league here, any help appreciated. I
am not even sure which part of the Appender code is activated.
cheers
More information about the Digitalmars-d-learn
mailing list