gcc 的 asm 易失性相当于 gfortran 的递归默认设置吗?

2023-11-26

我只是在玩递归函数C++ and Fortran我意识到一个简单的递归函数Fortran几乎是同类产品的两倍C++功能。现在,在讨论这个问题之前,我知道这里也有类似的问题,具体来说:

  1. 为什么添加汇编注释会导致生成的代码发生如此根本的变化?
  2. 工作的asm volatile(““ : : : “记忆”)
  3. 相当于gfortran中的asm volatility

然而,我有点更具体和困惑,因为 Fortran 编译器似乎正在做你可以实现的事情asm volatile in gcc。为了给您一些上下文,让我们考虑以下递归Fibonacci number执行:

Fortran 代码:

module test
implicit none
private
public fib

contains

! Fibonacci function
integer recursive function fib(n) result(r)
    integer, intent(in) :: n
    if (n < 2) then
        r = n
    else
        r = fib(n-1) + fib(n-2)
    end if
end function  ! end of Fibonacci function
end module

program fibonacci
use test, only: fib
implicit none
integer :: r,i 
integer :: n = 1e09
real(8) :: start, finish, cum_time

cum_time=0
do i= 1,n 
    call cpu_time(start)
    r = fib(20)
    call cpu_time(finish) 
    cum_time = cum_time + (finish - start)
    if (cum_time >0.5) exit
enddo  

print*,i,'runs, average elapsed time is', cum_time/i/1e-06, 'us' 
end program

编译为:

gfortran -O3 -march=native

C++代码:

#include <iostream>
#include <chrono>
using namespace std;

// Fib function
int fib(const int n)
{
    int r;
    if (n < 2)
        r = n;
    else
        r = fib(n-1) + fib(n-2);
    return r;
} // end of fib

template<typename T, typename ... Args>
double timeit(T (*func)(Args...), Args...args)
{
    double counter = 1.0;
    double mean_time = 0.0;
    for (auto iter=0; iter<1e09; ++iter){
        std::chrono::time_point<std::chrono::system_clock> start, end;
        start = std::chrono::system_clock::now();

        func(args...);

        end = std::chrono::system_clock::now();
        std::chrono::duration<double> elapsed_seconds = end-start;

        mean_time += elapsed_seconds.count();
        counter++;

        if (mean_time > 0.5){
            mean_time /= counter;
            std::cout << static_cast<long int>(counter)
            << " runs, average elapsed time is "
            << mean_time/1.0e-06 << " \xC2\xB5s" << std::endl; 
            break;
        }
    }
    return mean_time;
}

int main(){
    timeit(fib,20);
    return 0;
}

编译为:

g++ -O3 -march=native

Timing:

Fortran: 24991 runs, average elapsed time is 20.087 us
C++    : 12355 runs, average elapsed time is 40.471 µs

So gfortran那里的速度是两倍gcc。看看汇编代码,我得到

汇编(Fortran):

.L28:
    cmpl    $1, %r13d
    jle .L29
    leal    -8(%rbx), %eax
    movl    %ecx, 12(%rsp)
    movl    %eax, 48(%rsp)
    leaq    48(%rsp), %rdi
    leal    -9(%rbx), %eax
    movl    %eax, 16(%rsp)
    call    __bench_MOD_fib
    leaq    16(%rsp), %rdi
    movl    %eax, %r13d
    call    __bench_MOD_fib
    movl    12(%rsp), %ecx
    addl    %eax, %r13d

汇编(C++):

.L28:
    movl    72(%rsp), %edx
    cmpl    $1, %edx
    movl    %edx, %eax
    jle .L33
    subl    $3, %eax
    movl    $0, 52(%rsp)
    movl    %eax, %esi
    movl    %eax, 96(%rsp)
    movl    92(%rsp), %eax
    shrl    %eax
    movl    %eax, 128(%rsp)
    addl    %eax, %eax
    subl    %eax, %esi
    movl    %edx, %eax
    subl    $1, %eax
    movl    %esi, 124(%rsp)
    movl    %eax, 76(%rsp)

两个汇编代码都由几乎相似的块/标签一遍又一遍地重复组成。正如您所看到的,Fortran 程序集进行了两次调用fib函数,而在 C++ 汇编中,gcc可能已展开所有可能需要更多堆栈的递归调用push/pop和尾跳。

现在,如果我像这样在 C++ 代码中添加一个内联汇编注释

修改后的C++代码:

// Fib function
int fib(const int n)
{
    int r;
    if (n < 2)
        r = n;
    else
        r = fib(n-1) + fib(n-2);
    asm("");
    return r;
} // end of fib

生成的汇编代码,更改为

汇编(C++ 修改):

.L7:
    cmpl    $1, %edx
    jle .L17
    leal    -4(%rbx), %r13d
    leal    -5(%rbx), %edx
    cmpl    $1, %r13d
    jle .L19
    leal    -5(%rbx), %r14d
    cmpl    $1, %r14d
    jle .L55
    leal    -6(%rbx), %r13d
    movl    %r13d, %edi
    call    _Z3fibi
    leal    -7(%rbx), %edi
    movl    %eax, %r15d
    call    _Z3fibi
    movl    %r13d, %edi
    addl    %eax, %r15d

您现在可以看到两个调用fib功能。给他们计时让我

Timing:

Fortran: 24991 runs, average elapsed time is 20.087 us
C++    : 25757 runs, average elapsed time is 19.412 µs

我知道效果asm没有输出并且asm volatile是为了抑制激进的编译器优化,但在这种情况下,gcc认为它太聪明了,但最终生成了效率较低的代码。

所以问题是:

  • Why can gcc没有看到这个“优化”,当gfortan显然可以吗?
  • 内联装配线必须位于 return 语句之前。放在别处就没有效果了。为什么?
  • 此行为是编译器特定的吗?例如,您可以使用 clang/MSVC 模仿相同的行为吗?
  • 有没有安全的方法可以使递归更快C or C++(不依赖内联汇编或迭代式编码)?也许可变参数模板?

UPDATE:

  • 上面显示的结果都是gcc 4.8.4。我也尝试过用它来编译它gcc 4.9.2 and gcc 5.2我得到相同的结果。
  • 这个问题也可以被复制(修复?)如果不是把asm我将输入参数声明为易失性,即(volatile int n)代替(const int n),尽管这会导致我的机器上的运行时间稍微慢一些。
  • As 迈克尔·凯彻已经提到过,我们可以通过-fno-optimize-sibling-calls标志来解决这个问题。由于该标志被激活于-O2级别及以上,甚至编译-O1解决了这个问题。
  • 我已经运行了相同的示例clang 3.5.1 with -O3 -march=native尽管情况并不完全相同,clang似乎还可以生成更快的代码asm.

铿锵时间:

clang++ w/o asm    :  8846 runs, average elapsed time is 56.4555 µs
clang++ with asm   : 10427 runs, average elapsed time is 47.8991 µs 

请参阅本答案末尾附近的粗体字,了解如何获得由 gcc 生成的快速程序。阅读答案以获取对四个问题的答复。

你的第一个问题假设gfortran能够看到优化的可能性gcc没能看到。事实上,情况正好相反。gcc确定了一些它认为是优化可能性的东西,同时gfortran错过了。唉,gcc是错误的,它应用的优化结果是你的系统速度损失了 100%(与我的系统相当)。

回答你的第二个问题:asm声明阻止了内部转变,使得gcc查看错误优化的可能性。如果没有asm声明,您的代码已(有效)转换为:

int fib(const int n)
{
    if (n < 2)
        return n;
    else
        return fib(n-1) + fib(n-2);
}

包含递归调用的 return 语句会触发“兄弟调用优化”,从而使您的代码变得悲观。包含 asm 语句可防止在其中移动返回指令。

目前,我手头只有 gcc,所以我无法尝试其他编译器的行为来通过证据回答你的第三个问题,但这似乎绝对依赖于编译器。您遇到了 gcc 的一个怪癖(或错误,无论您如何称呼它),它在尝试优化它时会生成错误的代码。不同编译器的优化器有很大不同,因此其他编译器很可能不会像这样错误地优化您的代码gcc做。另一方面,用于优化的代码转换是一个经过深入研究的主题,并且大多数编译器都在实现类似的优化方法,因此另一个编译器可能会陷入与以下相同的陷阱:gcc.

解决最后一个问题:这不是 C/C++ 与 Fortran 的问题,而是关于gcc这会弄乱这个示例程序(以及可能的类似生产程序)。所以没有办法使递归更快C++,但是有一种方法可以加快这个例子的速度in gcc,通过禁用有问题的优化:-fno-optimize-sibling-calls,这会导致(在我的系统上,在一次测试运行中)比仅仅插入更快的代码asm陈述。

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

gcc 的 asm 易失性相当于 gfortran 的递归默认设置吗? 的相关文章

  • XamlReader.Load 在后台线程中。是否可以?

    WPF 应用程序具有从单独的文件加载用户控件的操作 使用XamlReader Load method StreamReader mysr new StreamReader pathToFile DependencyObject rootOb
  • 存储来自其他程序的事件

    我想将其他应用程序的事件存储在我自己的应用程序中 事件示例 打开 最小化 Word 或打开文件时 这样的事可能吗 运行程序 http msdn microsoft com en us library ms813609 aspx and 打开
  • 在 C# 中循环遍历文件文件夹的最简单方法是什么?

    我尝试编写一个程序 使用包含相关文件路径的配置文件来导航本地文件系统 我的问题是 在 C 中执行文件 I O 这将是从桌面应用程序到服务器并返回 和文件系统导航时使用的最佳实践是什么 我知道如何谷歌 并且找到了几种解决方案 但我想知道各种功
  • 使用 C 语言使用 strftime() 获取缩写时区

    我看过this https stackoverflow com questions 34408909 how to get abbreviated timezone and this https stackoverflow com ques
  • 获取 WPF 控件的所有附加事件处理程序

    我正在开发一个应用程序 在其中动态分配按钮的事件 现在的问题是 我希望获取按钮单击事件的所有事件 因为我希望删除以前的处理程序 我尝试将事件处理程序设置为 null 如下所示 Button Click null 但是我收到了一个无法分配 n
  • 将 Excel 导入到 Datagridview

    我使用此代码打开 Excel 文件并将其保存在 DataGridView 中 string name Items string constr Provider Microsoft Jet OLEDB 4 0 Data Source Dial
  • Rx 中是否有与 Task.ContinueWith 运算符等效的操作?

    Rx 中是否有与 Task ContinueWith 运算符等效的操作 我正在将 Rx 与 Silverlight 一起使用 我正在使用 FromAsyncPattern 方法进行两个 Web 服务调用 并且我想这样做同步地 var o1
  • 如何编写一个同时需要请求和响应Dtos的ServiceStack插件

    我需要提供本地化数据服务 所有本地化的响应 Dto 都共享相同的属性 IE 我定义了一个接口 ILocalizedDto 来标记那些 Dto 在请求端 有一个ILocalizedRequest对于需要本地化的请求 Using IPlugin
  • 有人可以提供一个使用 Amazon Web Services 的 itemsearch 的 C# 示例吗

    我正在尝试使用 Amazon Web Services 查询艺术家和标题信息并接收回专辑封面 使用 C 我找不到任何与此接近的示例 所有在线示例都已过时 并且不适用于 AWS 的较新版本 有一个开源项目CodePlex http www c
  • C++ 密码屏蔽

    我正在编写一个代码来接收密码输入 下面是我的代码 程序运行良好 但问题是除了数字和字母字符之外的其他键也被读取 例如删除 插入等 我知道如何避免它吗 特q string pw char c while c 13 Loop until Ent
  • 为什么在setsid()之前fork()

    Why fork before setsid 守护进程 基本上 如果我想将一个进程与其控制终端分离并使其成为进程组领导者 我使用setsid 之前没有分叉就这样做是行不通的 Why 首先 setsid 将使您的进程成为进程组的领导者 但它也
  • NASM Assembly 16bit“操作码和操作数的组合无效”

    所以我正在尝试编写一个程序来创建一个文件并在其中创建我的名字 但我得到了 操作码和操作数的无效组合 这mov handle ax 我不知道为什么 我看见here https stackoverflow com questions 29569
  • Process.Start() 方法在什么情况下返回 false?

    From MSDN https msdn microsoft com en us library e8zac0ca v vs 110 aspx 返回值 true 表示有新的进程资源 开始了 如果由 FileName 成员指定的进程资源 St
  • Server.MapPath - 给定的物理路径,预期的虚拟路径

    我正在使用这行代码 var files Directory GetFiles Server MapPath E ftproot sales 在文件夹中查找文件 但是我收到错误消息说 给定物理路径但虚拟路径 预期的 我对在 C 中使用 Sys
  • 如何在按钮单击时模拟按键 - Unity

    我对 Unity 中的脚本编写非常陌生 我正在尝试创建一个按钮 一旦单击它就需要模拟按下 F 键 要拾取一个项目 这是我当前的代码 在编写此代码之前我浏览了所有统一论坛 但找不到任何有效的东西 Code using System Colle
  • 线程和 fork()。我该如何处理呢? [复制]

    这个问题在这里已经有答案了 可能的重复 多线程程序中的fork https stackoverflow com questions 1235516 fork in multi threaded program 如果我有一个使用 fork 的
  • 使用 GhostScript.NET 打印 PDF DPI 打印问题

    我在用GhostScript NET http ghostscriptnet codeplex com打印 PDF 当我以 96DPI 打印时 PDF 打印效果很好 但有点模糊 如果我尝试以 600DPI 打印文档 打印的页面会被极大地放大
  • 当另一个线程可能设置共享布尔标志(最多一次)时,是否可以读取共享布尔标志而不锁定它?

    我希望我的线程能够更优雅地关闭 因此我尝试实现一个简单的信号机制 我不认为我想要一个完全事件驱动的线程 所以我有一个工作人员有一种方法可以使用关键部分优雅地停止它Monitor 相当于C lock我相信 绘图线程 h class Drawi
  • 如何使用 Word Automation 获取页面范围

    如何使用办公自动化找到 Microsoft Word 中第 n 页的范围 似乎没有 getPageRange n 函数 并且不清楚它们是如何划分的 这就是您从 VBA 执行此操作的方法 转换为 Matlab COM 调用应该相当简单 Pub
  • 在客户端系统中安装后桌面应用程序无法打开

    我目前正在使用 Visual Studio 2017 和 4 6 1 net 框架 我为桌面应用程序创建了安装文件 安装程序在我的系统中完美安装并运行 问题是安装程序在其他计算机上成功安装 但应用程序无法打开 edit 在客户端系统中下载了

随机推荐