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

Reply via email to