c++ - 无法在 C++ 中使用带有 -O3 选项的 clang 进行尾递归
问题描述
我无法告诉 clang 编译器在我的 C++ 代码中执行尾递归优化。我看到了这篇文章,如果有的话,C++ 编译器会进行尾递归优化吗?,建议将 -O3 标志与 clang 一起使用,它会做到这一点。但尽管如此,对于下面的素性测试代码的大量输入,我遇到了堆栈溢出。
#include <iostream>
#include <chrono>
#include <gmp.h>
#include <string>
bool mpz_is_divisible(mpz_t n, mpz_t d);
void mpz_sqr(mpz_t res, mpz_t x) {
//computes square of x and puts the result into res
mpz_pow_ui(res, x, 2);
}
//the below function causes stack overflow for large inputs
void smallest_divisor(mpz_t n, mpz_t div, mpz_t div_sqr, mpz_t smallest_div) {
//finds the smallest number which divides n and puts the result into smallest_div
mpz_sqr(div_sqr, div);
if (mpz_cmp(div_sqr, n) > 0) {
mpz_set(smallest_div, n);
return;
}
if (mpz_is_divisible(n, div)) {
mpz_set(smallest_div, div);
return;
}
mpz_add_ui(div, div, 1);
smallest_divisor(n, div, div_sqr, smallest_div); //<-- should do tail recursion optimisation?
}
bool mpz_prime_test_basic(mpz_t n) {
//checks if n is prime
mpz_t div, div_sqr, smallest_div;
mpz_inits(div, div_sqr, smallest_div, NULL);
mpz_set_ui(div, 2);
smallest_divisor(n, div, div_sqr, smallest_div);
if (mpz_cmp(smallest_div, n) == 0) {
mpz_clear(div);
mpz_clear(div_sqr);
mpz_clear(smallest_div);
return true;
}
mpz_clear(div);
mpz_clear(div_sqr);
mpz_clear(smallest_div);
return false;
}
bool mpz_is_divisible(mpz_t n, mpz_t d) {
//checks if n is divisible by d
mpz_t rem;
mpz_init(rem);
mpz_tdiv_r(rem, n, d);
int cmp = mpz_cmp_si(rem, 0); //checks if remainder is equal to 0
if (cmp == 0) return true;
return false;
}
int main() {
std::string num_str;
mpz_t num;
mpz_init(num);
std::cout << "Enter number to check" << '\n';
std::cin >> num_str;
mpz_set_str(num, num_str.c_str(), 10);
bool is_prime = mpz_prime_test_basic(num);
if (is_prime) {
std::cout << num_str << " is a prime\n";
} else {
std::cout << num_str << " is not a prime\n";
}
return 0;
}
我现在正在阅读 SICP 的第 1 章,并且有一个关于使用 O(√n) 算法检查素数的练习 (1.22)。尾递归由 Scheme 标准强制执行,因此对于大输入没有问题,但我在 C++ 中做同样的问题,对于大数,最小除数函数会消耗堆栈。我正在使用 GMP 库进行大整数运算。
是否可以在这样的程序中强制执行尾递归?
谢谢
解决方案
有点奇怪mpz_is_divisible
。一方面,您忘记释放rem
. 即使您添加mpz_clear(rem)
呼叫,您也不会在smallest_divisor
. 您需要做的是标记它__attribute__((noinline))
以让 Clang 进行优化(尽管 GCC 在没有它的情况下会这样做)。很奇怪,也没有实际意义,因为mpz_is_divisible
它只是mpz_divisible_p
.
void smallest_divisor(mpz_t n, mpz_t div, mpz_t div_sqr, mpz_t smallest_div) {
mpz_sqr(div_sqr, div);
if (mpz_cmp(div_sqr, n) > 0) {
mpz_set(smallest_div, n);
return;
}
if (mpz_divisible_p(n, div)) {
mpz_set(smallest_div, div);
return;
}
mpz_add_ui(div, div, 1);
smallest_divisor(n, div, div_sqr, smallest_div); // gets TCO
}
Godbolt(从主干 GCC 窃取 GMP,可能必须使用当前日期更新某些路径才能使其工作)
我还清理了很多代码以生成新版本。特别是,我的 FP 直觉告诉我,这smallest_divisor
真的不应该是它自己的功能。它是一个递归工作者,属于mpz_prime_test_basic
. 我们可以使用这个答案来编写本地递归定义。我gmpxx.h
在 Godbolt 上找不到,所以我也写了一个mpz_class
. 然后我们可以写
bool is_prime(mpz_t n) noexcept {
mpz div(2L);
fix{[](auto rec, mpz_t n, mpz_t div, mpz_t div_sqr) noexcept -> void {
mpz_mul(div_sqr, div, div);
if(mpz_cmp(div_sqr, n) > 0) mpz_set(div, n);
else if(!mpz_divisible_p(n, div)) {
mpz_add_ui(div, div, 1);
rec(n, div, div_sqr);
}
}}(n, div, mpz());
return mpz_cmp(div, n) == 0;
}
由于我们实际上并没有改变调用之间的参数,只是在它们后面发生变异,我们也可以只写
bool is_prime(mpz_t n) noexcept {
mpz div(2L), div_sqr;
fix{[&](auto rec) noexcept -> void {
mpz_mul(div_sqr, div, div);
if(mpz_cmp(div_sqr, n) > 0) mpz_set(div, n);
else if(!mpz_divisible_p(n, div)) {
mpz_add_ui(div, div, 1);
rec();
}
}}();
return mpz_cmp(div, n) == 0;
}
Godbolt(同样的警告)
推荐阅读
- javascript - 将角度项目从 7 更新到 10 后 Hammer_gesture_config 导入错误
- javascript - 开玩笑:测试是否调用了 addEventListener 的回调函数
- java - 在 groovy 脚本中扩展 java.lang.String
- python - 在 Tensorflow 中编写具有可训练参数的自己的层
- flutter - 使用颤振单击图表中显示的项目时重新创建步进线网络视图问题
- c# - 如何在其他用户控件中按名称查找元素?
- java - 用 ArrayList 中的内容填充二维数组
- python - 不同烧瓶项目的环境变量问题
- r - R cmd检查注意:无法验证当前时间
- php - Joomla - joomla 中的 PHP 会话消失