编译器如何如此好地优化这个阶乘函数?

2024-06-19

所以我一直在研究一些神奇的东西O3在 GCC 中(实际上我正在使用 Clang 进行编译,但这与 GCC 相同,我猜优化器的很大一部分是从 GCC 转移到 Clang 的)。

考虑这个 C 程序:

int foo(int n) {
    if (n == 0) return 1;
    return n * foo(n-1);
}

int main() {
    return foo(10);
}

第一件事让我非常惊叹(在这个问题中也让我惊叹 -https://stackoverflow.com/a/414774/1068248 https://stackoverflow.com/a/414774/1068248)是怎样的int foo(int)(一个基本的阶乘函数)编译成一个紧密的循环。这是它的 ARM 汇编:

    .globl  _foo
    .align  2
    .code   16
    .thumb_func _foo
_foo:
    mov r1, r0
    movs    r0, #1
    cbz r1, LBB0_2
LBB0_1:
    muls    r0, r1, r0
    subs    r1, #1
    bne LBB0_1
LBB0_2:
    bx  lr

天哪,我想。这很有趣!完全紧密循环来执行阶乘。哇。这不是尾部调用优化,因为它不是尾部调用。但它似乎做了非常相似的优化。

现在看看main:

    .globl  _main
    .align  2
    .code   16
    .thumb_func _main
_main:
    movw    r0, #24320
    movt    r0, #55
    bx  lr

说实话,这让我大吃一惊。简直就是完全绕过foo并返回3628800这是10!.

这让我真正意识到你的编译器在优化代码方面通常可以比你做得更好。但这提出了一个问题,它是如何做到如此出色的工作的?那么,任何人都可以解释(可能通过链接到相关代码)以下优化是如何工作的:

  1. 最初的foo优化成为一个紧密的循环。

  2. 优化在哪里main只是直接返回结果而不是实际执行foo.

这个问题的另一个有趣的副作用是展示 GCC/Clang 可以做的一些更有趣的优化。


如果你编译gcc -O3 -fdump-tree-all,您可以看到第一个将递归变成循环的转储是foo.c.035t.tailr1。这意味着处理其他尾部调用的相同优化也可以处理这种稍微扩展的情况。递归形式为n * foo(...) or n + foo(...)手动处理并不难(见下文),并且由于可以准确描述如何处理,因此编译器可以自动执行该优化。

的优化main更简单:内联可以将其变成10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 * 1,如果乘法的所有操作数都是常量,则可以在编译时执行乘法。

Update:以下是如何手动删除递归的方法foo,这可以自动完成。我并不是说这是 GCC 使用的方法,但这是一种现实的可能性。

首先,创建一个辅助函数。它的行为完全一样foo(n),只不过它的结果乘以一个额外的参数f.

int foo(int n)
{
    return foo_helper(n, 1);
}

int foo_helper(int n, int f)
{
    if (n == 0) return f * 1;
    return f * n * foo(n-1);
}

然后,依次递归调用foo递归调用foo_helper,并依靠因子参数来摆脱乘法。

int foo(int n)
{
    return foo_helper(n, 1);
}

int foo_helper(int n, int f)
{
    if (n == 0) return f;
    return foo_helper(n-1, f * n);
}

把它变成一个循环:

int foo(int n)
{
    return foo_helper(n, 1);
}

int foo_helper(int n, int f)
{
restart:
    if (n == 0) return f;
    {
        int newn = n-1;
        int newf = f * n;
        n = newn;
        f = newf;
        goto restart;
    }
}

最后,内联foo_helper:

int foo(int n)
{
    int f = 1;
restart:
    if (n == 0) return f;
    {
        int newn = n-1;
        int newf = f * n;
        n = newn;
        f = newf;
        goto restart;
    }
}

(当然,这不是手动编写函数的最明智的方法。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编译器如何如此好地优化这个阶乘函数? 的相关文章

随机推荐

  • 在 pywin32 中创建一个新的 Excel 文件

    我正在编写一个程序 概括来说 采用记事本文件并将其另存为 Excel 文件 现在我的程序打开一个我创建的空白 Excel 文件 只是 Book1 xls xlApp Dispatch Excel Application xlApp Visi
  • 如何在 Angular 中确定上一页 URL?

    假设我当前位于具有 URL 的页面上 user id 现在从本页导航到下一页 id posts 现在有没有办法让我可以检查之前的 URL 是什么 即 user id 以下是我的路线 export const routes Routes pa
  • 无法加载驱动程序类 org.mariadb.jdbc.Driver

    我想在 Spring Boot 中配置 2 个 JNDI 数据源 我尝试了这个配置 应用程序属性 spring production datasource jndi name java global production gateway s
  • 分离并重新附加“tools:rstudio”

    又名玩火 以下不起作用 rstd obj lt as environment tools rstudio detach tools rstudio attach rstd obj name tools rstudio 好吧 它似乎有效 但随
  • 在设备驱动程序中传递自定义标志以“打开”

    我需要将一些自定义标志传递给open 我的设备驱动程序的调用 我在LDD3中找到了这个例子 int dev open struct inode inode struct file filp if filp gt f flags O ACCM
  • ionic 2 本地存储无法将检索到的值设置为变量

    我试图将从 get 函数检索到的值设置为在外部声明的变量 但无法这样做 var dt retrieve this local get didTutorial then value gt alert value dt value consol
  • Facebook C# SDK - 服务器流身份验证

    我正在使用通过 NuGet 安装的 Facebook C SDK 以允许用户使用 Facebook 登录我的网站 在我找到的所有 C SDK 文档中 访问令牌是使用 JavaScript SDK 获取的 我想在不使用 JavaScript
  • 大 ר 符号到底代表什么?

    我真的很困惑大 O 大 Omega 和大 Theta 表示法之间的区别 我知道大 O 是上限 大 Omega 是下限 但是大 theta 到底代表什么 我读过这意味着紧束缚 但是 这是什么意思 首先我们来了解一下什么是大O 大Theta和大
  • Hibernate:hbm2ddl.auto=生产中更新?

    是否可以运行配置为 Hibernate 的应用程序hbm2ddl auto update在生产环境中更新数据库架构 不 这不安全 尽管 Hibernate 团队尽了最大努力 但您根本不能依赖自动更新生产中 编写您自己的补丁 与 DBA 一起
  • 无法将 TXT 记录设置为 Freenom 提供商中的域

    我想为分配给 Azure 中 WordPress 的域启用 SSL 我的域名是在 Freenom 中创建的 要完成该过程 我需要从 Azure 手动验证域 Azure 域验证 https i stack imgur com 4park jp
  • HttpClient SSLException

    我尝试向 Web 服务发送 https 请求 经过几次成功的尝试后 我开始不断收到此错误 这个错误是什么意思 为什么它第一次发生 javax net ssl SSLException java lang RuntimeException C
  • 什么可能会在一台服务器上导致此错误,而在另一台服务器上则不会?

    我们有一个连接到外部 Web 服务的 ASP Net 网站 几天前它突然停止工作 基本代码是这样的 Try request New ExternalWebService ProcessRequestService Error occurs
  • 如何解决这一 Mercurial 冲突?

    我对 Mercurial 和 Python 感到沮丧 因为它们让简单的事情变得困难 我有一个微不足道的冲突 由于 Mercurial 没有给出任何建议 我什至不知道如何解决这个微不足道的文件冲突 冲突是微不足道的 但如果我不能解决这个问题
  • Mac OS X Yosemite 中的 Node.js dtrace 错误

    我在 Mac OS X 10 10 Yosemite 上尝试使用 DTrace Node js 应用程序 sudo dtrace n profile 97 execname node arg1 jstack 150 8000 count t
  • 从 .resx 文件组获取所有可用区域性

    我需要以编程方式列出 resx 文件组中的可用区域性 但 ResourceManager 类似乎没有帮助 我可能有 Labels resx Labels fr FR resx Labels ro RO resx 等等 但是 我如何在运行时找
  • 如何在实体框架中完全锁定一行

    我正在处理的情况是我们正在处理金钱交易 例如 我有一个用户钱包表 其余额位于该行 UserId Wallet Id Balance 现在 在我们的网站和网络服务中 每次发生特定交易时 我们都需要 检查是否有足够的资金可用于执行该交易 从余额
  • 在 Flurry 中记录比错误 ID 更多信息的方法?

    我目前使用 iOS 版 Flurry 5 4 0 我担心在方法方面是否能够记录更多信息 而不仅仅是错误 ID void logError NSString errorID message NSString message error NSE
  • SFINAE:检测类是否具有自由函数[重复]

    这个问题在这里已经有答案了 有没有办法使用 SFINAE 来检测给定类的自由函数是否重载 基本上 我有以下解决方案 struct has no f struct has f void f has f const x template
  • NUnit 与 xUnit

    两者有什么区别NUnit http www nunit org and xUnit net https xunit net 开发其中两个而不是仅一个有什么意义 我读到 xUnit 是由 NUnit 的发明者开发的 xUnit net 是 N
  • 编译器如何如此好地优化这个阶乘函数?

    所以我一直在研究一些神奇的东西O3在 GCC 中 实际上我正在使用 Clang 进行编译 但这与 GCC 相同 我猜优化器的很大一部分是从 GCC 转移到 Clang 的 考虑这个 C 程序 int foo int n if n 0 ret