Consider this code in Appender
void put(U)(U item) if (canPutItem!U)
{
static if (isSomeChar!T && isSomeChar!U && T.sizeof < U.sizeof)
{
/* may throwable operation:
* - std.utf.encode
*/
// must do some transcoding around here
import std.utf : encode;
Unqual!T[T.sizeof == 1 ? 4 : 2] encoded;
auto len = encode(encoded, item);
put(encoded[0 .. len]);
}
else
{
ensureAddable(1);
immutable len = _data.arr.length;
import std.conv : emplaceRef;
auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T)item);
//We do this at the end, in case of exceptions
_data.arr = bigData;
}
}
Manually inline-ing the call to emplaceRef for basic types leads to 3x faster code. Replace the non-char type code path with this code,
static if (isBasicType!U)
{
auto d = _data.arr.ptr[0 .. len + 1];
d[len] = cast(Unqual!T) item;
_data.arr = d;
}
else
{
import std.conv : emplaceRef;
auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T)item);
//We do this at the end, in case of exceptions
_data.arr = bigData;
}
Functionally, these different code paths are exactly the same.
Here's the numbers before and after
Before: 3 secs, 29 ms, and 842 μs
After: 1 sec, 109 ms, 734 μs, and 6 hnsecs
Comment #1 by jack — 2017-02-20T18:32:39Z
Created attachment 1637
Appender Benchmark
Comment #2 by jack — 2017-03-03T22:43:39Z
BTW, making this @safe by changing the manual code to
static if (isBasicType!U)
{
auto d = (() @trusted => _data.arr.ptr[0 .. len + 1])();
d[len] = cast(Unqual!T) item;
_data.arr = d;
}
makes the code twice as slow as the other manual version
Comment #3 by robert.schadek — 2024-12-13T18:51:40Z