在这种情况下,带有 noexcept 的 C++ 函数实际上更慢?
C++ function with noexcept in this case is actually slower?
我正在尝试自己在不同的编译器上试验代码。
我一直在尝试查找禁用某些函数异常的优点(通过二进制足迹)并将其与不禁用异常的函数进行比较,实际上我偶然发现了一个奇怪的情况,其中最好有异常不是。
我一直在使用 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()
,我没有请求任何其他操作,我希望二进制文件能够反映这一点。
谢谢能解释的人!
Why is clang doing this extra code for us?
因为函数的行为不同。
I didn't request any other action but calling new()
通过声明函数 noexcept
,您已请求调用 std::terminate
以防异常传播到函数之外。
allocated_int
在第一个程序中从不调用 std::terminate
,而
allocated_int
在第二个程序中可能会调用std::terminate
。请注意,如果您记得启用优化器,添加的代码量会少得多。比较未优化的程序集大多是徒劳的。
您可以使用非抛出分配来防止:
return new(std::nothrow) int{};
这确实是一个敏锐的观察,即在非抛出函数中执行潜在的抛出操作可能会引入一些额外的工作,如果在潜在的抛出函数中执行相同的操作则不需要完成这些工作。
I've been trying to lookup the advantages of disabling exceptions on certain functions
使用非抛出的优点可能会在调用此类函数的地方实现;不在函数本身内。
没有 nothrow
,您的函数只是作为您调用的分配函数的前端。它自己没有任何实际行为。事实上,在真正的可执行文件中,如果你进行 link 时间优化,它很有可能会完全消失。
当您添加 noexcept
时,您的代码会悄悄地转换成大致如下的内容:
auto* allocated_int()
{
try {
return new int{};
}
catch(...) {
terminate();
}
}
您看到生成的额外代码是捕获异常和调用 terminate
when/if 所需的代码。
我正在尝试自己在不同的编译器上试验代码。 我一直在尝试查找禁用某些函数异常的优点(通过二进制足迹)并将其与不禁用异常的函数进行比较,实际上我偶然发现了一个奇怪的情况,其中最好有异常不是。
我一直在使用 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()
,我没有请求任何其他操作,我希望二进制文件能够反映这一点。
谢谢能解释的人!
Why is clang doing this extra code for us?
因为函数的行为不同。
I didn't request any other action but calling new()
通过声明函数 noexcept
,您已请求调用 std::terminate
以防异常传播到函数之外。
allocated_int
在第一个程序中从不调用 std::terminate
,而
allocated_int
在第二个程序中可能会调用std::terminate
。请注意,如果您记得启用优化器,添加的代码量会少得多。比较未优化的程序集大多是徒劳的。
您可以使用非抛出分配来防止:
return new(std::nothrow) int{};
这确实是一个敏锐的观察,即在非抛出函数中执行潜在的抛出操作可能会引入一些额外的工作,如果在潜在的抛出函数中执行相同的操作则不需要完成这些工作。
I've been trying to lookup the advantages of disabling exceptions on certain functions
使用非抛出的优点可能会在调用此类函数的地方实现;不在函数本身内。
没有 nothrow
,您的函数只是作为您调用的分配函数的前端。它自己没有任何实际行为。事实上,在真正的可执行文件中,如果你进行 link 时间优化,它很有可能会完全消失。
当您添加 noexcept
时,您的代码会悄悄地转换成大致如下的内容:
auto* allocated_int()
{
try {
return new int{};
}
catch(...) {
terminate();
}
}
您看到生成的额外代码是捕获异常和调用 terminate
when/if 所需的代码。