【问题标题】:C++ function with noexcept in this case is actually slower?在这种情况下,带有 noexcept 的 C++ 函数实际上更慢?
【发布时间】:2021-10-22 03:01:45
【问题描述】:

我正在尝试自己在不同的编译器上使用代码。 我一直在尝试查找在某些函数上禁用异常的优势(通过二进制足迹)并将其与不禁用异常的函数进行比较,实际上我偶然发现了一个奇怪的情况,最好有异常不是。

我一直在使用 Matt Godbolt 的 Compiler Explorer 进行这些检查,并且在 x86-64 clang 12.0.1 上进行了检查,没有任何标志(在 GCC 上这种奇怪的行为不存在) .

看看这个简单的代码:

auto* allocated_int()
{
    return new int{};
}

int main()
{
    delete allocated_int();

    return 0;
}

非常直接,几乎删除了从函数allocated_int()返回的分配指针。

正如预期的那样,二进制文件的占用空间也很小:

allocated_int():                     # @allocated_int()
        push    rbp
        mov     rbp, rsp
        mov     edi, 4
        call    operator new(unsigned long)
        mov     rcx, rax
        mov     rax, rcx
        mov     dword ptr [rcx], 0
        pop     rbp
        ret

另外,非常直截了当。 但是,当我将 noexcept 关键字应用于 allocated_int() 函数时,二进制文件就会膨胀。我将在此处应用生成的程序集:

allocated_int():                     # @allocated_int()
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     edi, 4
        call    operator new(unsigned long)
        mov     rcx, rax
        mov     qword ptr [rbp - 8], rcx        # 8-byte Spill
        jmp     .LBB0_1
.LBB0_1:
        mov     rcx, qword ptr [rbp - 8]        # 8-byte Reload
        mov     rax, rcx
        mov     dword ptr [rcx], 0
        add     rsp, 16
        pop     rbp
        ret
        mov     rdi, rax
        call    __clang_call_terminate
__clang_call_terminate:                 # @__clang_call_terminate
        push    rax
        call    __cxa_begin_catch
        call    std::terminate()

clang 为什么要为我们做这个额外的代码?除了调用new(),我没有要求任何其他操作,我希望二进制文件能够反映这一点。

谢谢能解释的人!

【问题讨论】:

  • 你是如何申请noexcept的?您在使用noexcept(true) 吗?此外,检查调试构建代码几乎没有意义。编译器可以/将添加它/您可以用于调试目的的代码。
  • 开启优化 - 实时 - godbolt.org/z/69GTooYKz 未优化的代码有时会较大以使调试更容易,不应用作编译生成好代码还是坏代码的示例。
  • noexcept 不会“禁用异常”,它会改变异常的行为——让它们调用std::terminate
  • @NathanOliver 是的,我正在使用 noexcept(true) :)
  • @RichardCritten 我不想最大限度地优化优化,因为这无助于我了解编译器的自然行为。相反,我将其更改为 -O1,最小优化,它仍然比没有 noexcept 更大...

标签: c++ performance optimization clang noexcept


【解决方案1】:

clang 为什么要为我们做这个额外的代码?

因为函数的行为不同。

我没有请求任何其他操作,只是调用 new()

通过声明函数noexcept,您已请求调用std::terminate,以防异常传播到函数之外。

allocated_int 在第一个程序中从不调用std::terminate,而 第二个程序中的allocated_int 可以调用std::terminate。请注意,如果您记得启用优化器,添加的代码量会少得多。比较未优化的装配大多是徒劳的。

您可以使用非抛出分配来防止:

return new(std::nothrow) int{};

确实是一个敏锐的观察,在非抛出函数中执行潜在抛出的事情可能会引入一些额外的工作,如果在潜在抛出函数中完成相同的事情则不需要这样做。

我一直在尝试查找在某些函数上禁用异常的优点

使用非抛出的优势可能会在调用此类函数的地方实现;不在函数本身内。

【讨论】:

  • 无论如何,非抛出分配仍然会使二进制文件膨胀,但我想关闭优化器确实是徒劳的。感谢您提供信息丰富的评论!
【解决方案2】:

没有nothrow,你的函数只是作为你调用的分配函数的前端。它本身没有任何实际行为。事实上,在一个真正的可执行文件中,如果你进行链接时优化,它很有可能会完全消失。

当您添加noexcept 时,您的代码会默默地转换为大致如下所示的内容:

auto* allocated_int()
{
    try { 
        return new int{};
    }
    catch(...) { 
        terminate();
    }
}

您看到生成的额外代码是捕获异常并在需要时/如果需要时调用terminate 所需要的。

【讨论】:

    猜你喜欢
    • 2015-11-14
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    相关资源
    最近更新 更多