为何F#未内联内部的尾递归loop函数?
F#尾递归嵌套函数未内联的原因分析
你的loop函数确实是尾递归,F#编译器也已经把它内部的递归调用转换成了while循环,但它没有被内联到外层的活动模式函数中,而是生成了独立的Token.loop@16函数,核心原因如下:
多调用分支导致的代码膨胀风险:外层的
(|Integer|_|)活动模式有3个不同的分支都调用了loop。如果编译器把loop内联到每个调用点,会导致相同的循环代码重复3次,反而增加代码体积、降低缓存命中率。为了避免这种冗余,编译器选择保留独立的嵌套函数,只在各个分支处调用它。递归函数的内联限制:你尝试给
loop标记inline报错,是因为F#不允许直接标记递归函数为inline——递归内联会导致无限展开,编译器无法处理这种逻辑。只有非递归的嵌套函数才能直接被内联,递归函数的内联需要编译器做特殊的循环展开优化,但这里因为多分支调用的存在,编译器没有触发这个优化。尾递归转循环与内联是独立优化步骤:尾递归转循环是针对
loop函数内部的优化,把递归调用替换成while循环逻辑;而内联是把函数体直接替换到调用位置。这两步是分开执行的,所以你会看到loop@16内部是while块,但这个函数本身没有被内联到外层的调用处。
优化尝试方向
如果希望推动编译器内联,可以合并外层的分支逻辑,减少loop的调用分支数量,比如:
[<return: Struct>] let (|Integer|_|) self = let rec loop acc isNegative self = match self with | C(digit, tail) when Char.IsDigit digit -> loop (acc * 10UL + (uint64 digit - uint64 '0')) isNegative tail | tail -> ValueSome struct(struct(acc, isNegative), tail) match self with | C(sign, (C(digit, tail) as rest)) when (sign = '-' || sign = '+') && Char.IsDigit digit -> loop 0UL (sign = '-') rest | C(digit, _) as tail when Char.IsDigit digit -> loop 0UL false tail | _ -> ValueNone
合并正负号的判断分支后,loop的调用次数减少,编译器更有可能选择内联该函数。
内容的提问来源于stack exchange,提问作者Gabriel
相关产品推荐
相关产品推荐

