d的编译时成本
创建2022年dconf在线演示时,我在找构建约束的替代方法.在此验证概念.
我开始研究标准库的成本,考虑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来调用它.
可用null转R*,就有了"现成"的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'的实现.
现在,亚当有一个观点,如果已在其他地方使用了该方便的模板,那么它会对整体性能产生负面影响,因为模板答案的缓存可加快编译速度.此时,保证模板实例化是唯一的,因为这些都是λ式,所以这不适用.
浙公网安备 33010602011771号