为啥在这里使用 boost::multiprecision::cpp_int 会影响尾调用优化

Posted

技术标签:

【中文标题】为啥在这里使用 boost::multiprecision::cpp_int 会影响尾调用优化【英文标题】:Why does using boost::multiprecision::cpp_int affect tail call optimization here为什么在这里使用 boost::multiprecision::cpp_int 会影响尾调用优化 【发布时间】:2017-02-07 01:26:15 【问题描述】:

我有一些模板化代码,编译器可以对大多数数据类型进行尾调用优化,但不能对其他数据类型进行优化。代码实现pow()

template<typename T, typename U>
void powRecurse(T& x, U& y, T& acc)

   if(y == 0) 
      acc = Identity<T>;
      return;
   
   if(y == 1) 
      acc = acc * x;
      return;
   
   if(y % 2 == 1) 
      acc = acc * x;
      y = y - 1;
   
   x = x*x; 
   y = y/2;
   powRecurse<T, U>(x, y, acc);


template<typename T, typename U>
T tailPow(T x, U y)

   T rv = Identity<T>;
   powRecurse<T, U>(x, y, rv);
   return rv;

参数 T 的类型似乎对尾调用优化没有影响,我尝试过的任何类型都可以使用参数 U 的正确类型进行尾调用优化。如果参数 U 是 uint64_t,编译器可以进行尾调用优化.如果它是 boost::multiprecision::cpp_int 那么编译器不会尾调用优化。

我还尝试将 uint64_t 包装在一个类中,并将一个模板包装器包装在一个 int 类型上,这两个尾调用都进行了优化。

有什么理由不应该进行尾调用优化吗?显然我可以循环这个,但我真的只是想了解这里的语言或编译器问题。

【问题讨论】:

显而易见,但可能无用的评论:编译器在优化方面没有义务,而且它们是,afaik,一堆启发式,所以如果cpp_int“足够大”,那么编译器可能会认为尾递归确实不值得。这是很有趣的原因,但我怀疑如果不熟悉 gcc(或其他编译器)的内部知识就可以回答它。 @yeputons 谢谢,主要是我不确定 cpp_int 中没有什么东西使它不可能。另一方面,我无法想象尾调用优化会更糟的情况,尽管我可以想象这是内联后函数长度的问题以及分析器无法识别优化的可能性。 【参考方案1】:

惰性求值意味着所有“递归”函数调用实际上都是不同的函数模板实例化。

参见例如这里有类似的讨论:

Boost multiprecision : Recursive template instantiation exceeded maximum length 256 或这里How to use sqrt and ceil with Boost::multiprecision?

因此,您可以通过选择退出惰性评估模板表达式(boost::multiprecision::et_off,如链接中所述)来获得您期望的所有 tail-coll 实现细节,但请务必检查减少的代码 -尺寸和感知 TCO 优化实际上可以提高性能。

实际上,某些算法受益于可以跳过常见子表达式、重新排序无损操作等的表达式模板。

【讨论】:

我明白了!我想这可能也是为什么最初我必须为函数调用指定模板参数以避免与我传递的某些参数不匹配的类型。 是的。或者,您可以在传递之前显式转换参数。对于使用 Boost Multiprecision 的用户来说,这是一个普遍的惊喜。我希望他们在他们的文档中更清楚地表明T a,b; auto c = a+b; 并不意味着c 的类型是T,就像你的祖父C++ 类型一样:) 看来这并没有解决问题,尽管我还不确定这不是一个因素。

以上是关于为啥在这里使用 boost::multiprecision::cpp_int 会影响尾调用优化的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MongoDB 在这里使用 scanAndOrder?

为啥我必须在这里使用 group by?

为啥 MySQL 在这里并不总是使用索引合并?

为啥这里使用立即执行?

为啥在获取系统时间时在这里使用`atomic_signal_fence`

为啥在这里使用 `any` 会导致该程序超出递归深度,但使用 `for` 循环不会?