Thread overview
hasUDA alternatives?
Jan 28, 2023
Anonymouse
Jan 28, 2023
Hipreme
Jan 31, 2023
Anonymouse
January 28, 2023

I use hasUDA, getUDAs and getSymbolsByUDA fairly heavily in my project. dmd requires some 3.2Gb to compile it, a dub recompilation taking somewhere around 8-14 seconds, depending on the phase of the moon. It's not too bad, admittedly.

Stuff like this, naturally taken out of all context:

static if (isSerialisable!member)
{
    import std.path : buildNormalizedPath;

    static if (hasUDA!(this.tupleof[i], Resource))
    {
        member = buildNormalizedPath(state.settings.resourceDirectory, member);
    }
    else static if (hasUDA!(this.tupleof[i], Configuration))
    {
        member = buildNormalizedPath(state.settings.configDirectory, member);
    }
}
private alias allEventHandlerFunctionsInModule =
    Filter!(isSomeFunction, getSymbolsByUDA!(thisModule, IRCEventHandler));
enum isSetupFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.setup);
enum isEarlyFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.early);
enum isLateFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.late);
enum isCleanupFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.cleanup);
alias hasSpecialTiming = templateOr!(isSetupFun, isEarlyFun, isLateFun, isCleanupFun);
alias isNormalEventHandler = templateNot!hasSpecialTiming;

alias setupFuns = Filter!(isSetupFun, this.allEventHandlerFunctionsInModule);
alias earlyFuns = Filter!(isEarlyFun, this.allEventHandlerFunctionsInModule);
alias lateFuns = Filter!(isLateFun, this.allEventHandlerFunctionsInModule);
alias cleanupFuns = Filter!(isCleanupFun, this.allEventHandlerFunctionsInModule);
alias pluginFuns = Filter!(isNormalEventHandler, this.allEventHandlerFunctionsInModule);

If hasUDA and friends are so bad1 2 3, what can I use instead?

I need them to work at compile-time. hasUDA just needs to evaluate to true or false, but for getUDAs and getSymbolsByUDA I need them to resolve to symbols (and not string names of symbols).

Do I have any alternatives?

January 28, 2023

On Saturday, 28 January 2023 at 16:29:35 UTC, Anonymouse wrote:

>

I use hasUDA, getUDAs and getSymbolsByUDA fairly heavily in my project. dmd requires some 3.2Gb to compile it, a dub recompilation taking somewhere around 8-14 seconds, depending on the phase of the moon. It's not too bad, admittedly.

Stuff like this, naturally taken out of all context:

static if (isSerialisable!member)
{
    import std.path : buildNormalizedPath;

    static if (hasUDA!(this.tupleof[i], Resource))
    {
        member = buildNormalizedPath(state.settings.resourceDirectory, member);
    }
    else static if (hasUDA!(this.tupleof[i], Configuration))
    {
        member = buildNormalizedPath(state.settings.configDirectory, member);
    }
}
private alias allEventHandlerFunctionsInModule =
    Filter!(isSomeFunction, getSymbolsByUDA!(thisModule, IRCEventHandler));
enum isSetupFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.setup);
enum isEarlyFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.early);
enum isLateFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.late);
enum isCleanupFun(alias T) = (getUDAs!(T, IRCEventHandler)[0]._when == Timing.cleanup);
alias hasSpecialTiming = templateOr!(isSetupFun, isEarlyFun, isLateFun, isCleanupFun);
alias isNormalEventHandler = templateNot!hasSpecialTiming;

alias setupFuns = Filter!(isSetupFun, this.allEventHandlerFunctionsInModule);
alias earlyFuns = Filter!(isEarlyFun, this.allEventHandlerFunctionsInModule);
alias lateFuns = Filter!(isLateFun, this.allEventHandlerFunctionsInModule);
alias cleanupFuns = Filter!(isCleanupFun, this.allEventHandlerFunctionsInModule);
alias pluginFuns = Filter!(isNormalEventHandler, this.allEventHandlerFunctionsInModule);

If hasUDA and friends are so bad1 2 3, what can I use instead?

I need them to work at compile-time. hasUDA just needs to evaluate to true or false, but for getUDAs and getSymbolsByUDA I need them to resolve to symbols (and not string names of symbols).

Do I have any alternatives?

So, for that, having an exclude keyword would be nice. But for right now, I would like to check a better solution with you. Building my entire engine takes in LDC 250mb, in DMD I wasn't able to check but it seems to use really little.

From 2, FeepingCreature says that using udaIndex is better than using hasUDA and getUDAs.

getUDAs can be simply changed to __traits(getAttributes, yourMemberHere).

Many std.traits things actually uses Filter!() which is a recursive template using static if + AliasSeq. Recursive templates are super heavy. In general, recursive functions always spells "slow" in any place.

The best thing to avoid in phobos are those recursive kinds, specially if you're using for too many places.

So, what can I do if I wish to construct a better filter? Well. Construct an array of indices to your filtered types instead of an AliasSeq. It is impossible to construct procedurally an AliasSeq without using recursive templates. That way you could iterate through those indices instead.

It is a little more manual work, but it is better on long run specially when you're not going to keep changing your reflection functions any soon

So, avoid Filter which seems to be your main bottleneck there. Specially because you will iterate all members many times. It is always best to do a single iteration and using the same data.

Your code seems to be analogous to doing:

alias setupFuns = Filter!(isSetupFun, this.allEventHandlerFunctionsInModule);
alias earlyFuns = Filter!(isEarlyFun, this.allEventHandlerFunctionsInModule);

///Pseudo code expanded representation:


    template setupFuns alias pred)
    {
        alias Filter = AliasSeq!();
        static foreach (member; Module)
            static if (pred!arg)
                Filter = AliasSeq!(Filter, arg);
    }
    template earlyFuns(alias pred)
    {
        alias Filter = AliasSeq!();
        static foreach (member; Module)
            static if (pred!arg)
                Filter = AliasSeq!(Filter, arg);
    }

A better solution would be:


    static foreach(mem; __traits(allMembers, thisModule))
    {
        static if(is(typeof(__traits(getMember, thisModule, mem)) == EarlyFunc))
        else static if(is(typeof(__traits(getMember, thisModule, mem)) == LateFunc))

    }

That way, you would avoid: Recursive template executed a lot of times + you would use reflection once (iterate once vs iterate n times).

January 31, 2023

On Saturday, 28 January 2023 at 17:16:07 UTC, Hipreme wrote:

>

[...]

Thank you. I incorporated some of these ideas and looked up how to profile the compilation with tracy, and now dmd memory requirements are down to ~2100 Mb. It's still a far cry from your 250 Mb, however.

I changed it to only call getSymbolsByUDA once (per plugin module) and then filtered it by use of arrays of indices, like you suggested. This reduced memory needed by some 150 Mb; less than I had hoped but still a step in the right direction.

I then went through the compilation with tracy and removed, replaced or versioned out parts that the compiler seemed to spend a long time on in semantic analysis. I lost some functionality but the returns were considerable.

The remaining big offender seems to be std.concurrency.send. I consolidated some unique instantiations, but it's pretty core to how the thing works and I'm not sure what I can neatly do about that.