在这种情况下,带有 noexcept 的 C++ 函数实际上更慢?

Noa*_*rik 5 c++ optimization performance clang noexcept

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

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

看看这个简单的代码:

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

int main()
{
    delete allocated_int();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

非常简单,几乎删除了从函数返回的已分配指针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
Run Code Online (Sandbox Code Playgroud)

此外,非常直接。但是当我将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()
Run Code Online (Sandbox Code Playgroud)

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

谢谢能解释的人!

eer*_*ika 5

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

因为函数的行为是不同的。

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

通过声明 function noexcept,您已请求std::terminate在异常传播出函数时被调用。

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

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

return new(std::nothrow) int{};
Run Code Online (Sandbox Code Playgroud)

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

我一直在尝试查找在某些功能上禁用异常的优势

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