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