std.variant benchmark
Dmitry Olshansky
dmitry.olsh at gmail.com
Sun Jul 29 13:11:24 PDT 2012
On 29-Jul-12 22:59, Andrei Alexandrescu wrote:
> On 7/29/12 10:43 AM, Dmitry Olshansky wrote:
>> I'm horrified. Who was working on std.variant enhancements? Please chime
>> in.
>
> I guess you just volunteered! When I looked at it this morning I noticed
> a few signs of bit rot, e.g. opAssign returns by value and such. (Only
> planting a "ref" there improves performance a good amount.)
>
Strange, but numbers stay the same for me.
> Variant has a simple design with (in case of int) an int and a pointer
> to a function. Many of its operations incur an indirect call through
> that pointer. This makes operations slower than the time-honored design
> of using an integral tag and switching on it, but offers in return the
> ability to hold any type without needing to enumerate all types explicitly.
>
Well obviously integers do not take lightly when they are copied around
with memcpy like some pompous arrays that alone incurs *some* penalty.
In fact memcpy could and should be replaced with word by word copy for
almost all of struct sizes up to ~32 bytes (as size is known in advance
for this particular function pointer i.e. handler!int).
I've found something far more evil:
@property bool convertsTo(T)() const
{
TypeInfo info = typeid(T);
return fptr(OpID.testConversion, null, &info) == 0;
}
Okay... now let me pull off another piece of rag:
private VariantN opArithmetic(T, string op)(T other)
{
VariantN result;
static if (is(T == VariantN))
{
if (convertsTo!(uint) && other.convertsTo!(uint))
result = mixin("get!(uint) " ~ op ~ " other.get!(uint)");
else if (convertsTo!(int) && other.convertsTo!(int))
result = mixin("get!(int) " ~ op ~ " other.get!(int)");
else if (convertsTo!(ulong) && other.convertsTo!(ulong))
result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)");
else if (convertsTo!(long) && other.convertsTo!(long))
result = mixin("get!(long) " ~ op ~ " other.get!(long)");
else if (convertsTo!(double) && other.convertsTo!(double))
result = mixin("get!(double) " ~ op ~ "
other.get!(double)");
else
result = mixin("get!(real) " ~ op ~ " other.get!(real)");
}
else
{
if (is(typeof(T.max) : uint) && T.min == 0 &&
convertsTo!(uint))
result = mixin("get!(uint) " ~ op ~ " other");
else if (is(typeof(T.max) : int) && T.min < 0 &&
convertsTo!(int))
result = mixin("get!(int) " ~ op ~ " other");
else if (is(typeof(T.max) : ulong) && T.min == 0
&& convertsTo!(ulong))
result = mixin("get!(ulong) " ~ op ~ " other");
else if (is(typeof(T.max) : long) && T.min < 0 &&
convertsTo!(long))
result = mixin("get!(long) " ~ op ~ " other");
else if (is(T : double) && convertsTo!(double))
result = mixin("get!(double) " ~ op ~ " other");
else
result = mixin("get!(real) " ~ op ~ " other");
}
return result;
}
So to boot in order to get to int + int _detected_ it needs 4 calls to
fptr(OpID.testConversion, null, &info) == 0 and acquire TypeInfo each
time. Not counting 2 gets to obtain result. It's just madness.
Plain hardcoded Type to integer switch seems so much better now.
> We can use the pointer to function as a tag for improving performance on
> primitive types.
>
Indeed we can though it won't be as fast as simple tag - pointers
have quite big and sparse addresses giving us the same as:
if(ptr == handler!int)
...
else if(ptr == handler!uint)
...
else
...
Another way to go is make 2-d function table for all built-ins and all
operations on them. It's in fact a bunch of tables:
Type1 x Type2 for all of important basic operations. Tables are static
so it won't be much of a problem. At least int + int then would be
1 memory read, 1 call, 2 (casted as needed) reads & 1 store.
--
Dmitry Olshansky
More information about the Digitalmars-d
mailing list