d的编译时成本

原文

创建2022dconf在线演示时,我在找构建约束替代方法.在此验证概念.
我开始研究标准库的成本,考虑isInputRange的约束条件:

enum bool isInputRange(R) =
    is(typeof(R.init) == R)
    && is(ReturnType!((R r) => r.empty) == bool)
    && (is(typeof((return ref R r) => r.front)) ||
        is(typeof(ref (return ref R r) => r.front)))
    && !is(ReturnType!((R r) => r.front) == void)
    && is(typeof((R r) => r.popFront));

只看返回类型(ReturnType)模板?它接受参数(本例中是个λ函数)并计算可调用对象的返回类型.

但是语言有此功能,不是吗?是的,它叫typeof.typeof提供式的"类型",并且不需要额外语义计算,直接链接到编译器的语义分析.

看一下返回类型(ReturnType)模板(及其依赖):

template ReturnType(alias func)
if (isCallable!func)
{
    static if (is(FunctionTypeOf!func R == return))
        alias ReturnType = R;
    else
        static assert(0, "无中类型");
}

template FunctionTypeOf(alias func)
if (isCallable!func)
{
    static if ( (is(typeof(& func) Fsym : Fsym*) && is(Fsym == function)) || is(typeof(& func) Fsym == delegate))
    {
        alias FunctionTypeOf = Fsym; 
// 嵌套
    }
    else static if (is(typeof(& func.opCall) Fobj == delegate) || is(typeof(& func.opCall!()) Fobj == delegate))
    {
        alias FunctionTypeOf = Fobj;
// 可调用
    }
    else static if (
            (is(typeof(& func.opCall) Ftyp : Ftyp*) && is(Ftyp == function)) ||
            (is(typeof(& func.opCall!()) Ftyp : Ftyp*) && is(Ftyp == function))
        )
    {
        alias FunctionTypeOf = Ftyp; // 可调用
    }
    else static if (is(func T) || is(typeof(func) T))
    {
        static if (is(T == function))
            alias FunctionTypeOf = T;    // 函数
        else static if (is(T Fptr : Fptr*) && is(Fptr == function))
            alias FunctionTypeOf = Fptr; // 函数指针
        else static if (is(T Fdlg == delegate))
            alias FunctionTypeOf = Fdlg; // 闭包
        else
            static assert(0);
    }
    else
        static assert(0);
}

template isCallable(alias callable)
{
    // 20行
}

template isSomeFunction(alias T)
{
    // 15行
}

哇,为什么这么复杂?为确定返回类型,要用typeof原语,但这需要有效式.对可调用对象,表明需要一组有效参数.这些都需要库来内省,但只给了一个符号而没有环境.

但是却有环境!使用R确切知道如何调用构造的λ函数!
ReturnType可分发各种调用,而不仅是λ.
但是要生成式,是输入区间不需要构造,甚至不需要有效的R,它只需要现有的R来调用它.
可用nullR*,就有了"现成"R.是的,运行它会崩溃,但不需要运行它,只需要得到它的类型!
如下是是输入区间不用ReturnType的等价模板:

enum isInputRange(R) =
    is(typeof(R.init) == R)
    && is(typeof(() { return (*cast(R*)null).empty; }()) == bool)
    && (is(typeof((return ref R r) => r.front)) ||is(typeof(ref (return ref R r) => r.front)))
    && !is(typeof(() { return (*cast(R*)null).front; }()) == void)
    && is(typeof((R r) => r.popFront));

这里区别是有个无参λ,所以不必依赖库技巧内省来知道如何调用它.

衡量结果
给定与std.traits完全独立的是输入区间,结果如何?节约了多少?答案为:节约50%时间,65%空间.
每次调用返回类型独立的,因此执行自己的语义分析.使用编译器的v模板开关,可看到使用当前的标准库添加了相当多的依赖模板.

结语
使用std.traits时,要考虑编译时间成本.

这里

这没有简化ReturnType,它只是普通的λ函数.我现在这样:

template RT(alias sym) {
    static if(is(typeof(sym) R == return))
        alias RT = R;
    else
        static assert(false, "bad");
}

...

else version(useIsExpr)
{
    enum isInputRange(R) = is(typeof(R.init) == R)
    && is(RT!((R r) => r.empty) == bool)
    && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front)))
    && !is(RT!((R r) => r.front) == void)
    && is(typeof((R r) => r.popFront));
}

结果仍然不比直接使用typeof好,但是要好得多,与直接使用typeof相比,对10000个实例,它增加了大约0.15s总编译时间,及增加了100MB的内存使用.
观点仍然成立:在约束模板中,应尽量避免使用各种方便的模板.只要给出正确答案,没人关心'isInputRange'的实现.
现在,亚当有一个观点,如果已在其他地方使用了该方便的模板,那么它会对整体性能产生负面影响,因为模板答案的缓存可加快编译速度.此时,保证模板实例化唯一的,因为这些都是λ式,所以这不适用.

posted @   zjh6  阅读(14)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示