Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ function with noexcept in this case is actually slower?

I'm trying to experiment with code by myself here on different compilers. I've been trying to lookup the advantages of disabling exceptions on certain functions (via the binary footprint) and to compare that to functions that don't disable exceptions, and I've actually stumbled onto a weird case where it's better to have exceptions than not.

I've been using Matt Godbolt's Compiler Explorer to do these checks, and it was checked on x86-64 clang 12.0.1 without any flags (on GCC this weird behavior doesn't exist).

Looking at this simple code:

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

int main()
{
    delete allocated_int();

    return 0;
}

Very straight-forward, pretty much deletes an allocated pointer returned from the function allocated_int().

As expected, the binary footprint is minimal, as well:

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

Also, very straight-forward. But the moment I apply the noexcept keyword to the allocated_int() function, the binary bloats. I'll apply the resulting assembly here:

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()

Why is clang doing this extra code for us? I didn't request any other action but calling new(), and I was expecting the binary to reflect that.

Thank you for those who can explain!

like image 927
Noam Rodrik Avatar asked Aug 20 '21 14:08

Noam Rodrik


People also ask

Does Noexcept make code faster?

That noexcept keyword is tricky, but just know that if you use it, your coding world will spin faster.

What happens when Noexcept throws?

noexcept is an improved version of throw(), which is deprecated in C++11. Unlike pre-C++17 throw(), noexcept will not call std::unexpected, may or may not unwind the stack, and will call std::terminate, which potentially allows the compiler to implement noexcept without the runtime overhead of throw().

What does Noexcept mean?

The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions. It can be used within a function template's noexcept specifier to declare that the function will throw exceptions for some types but not others.

Should I use Noexcept everywhere?

In general, you should use noexcept when you think it will actually be useful to do so. Some code will take different paths if is_nothrow_constructible is true for that type. If you're using code that will do that, then feel free to noexcept appropriate constructors.

When should I use noexcept in C++?

The standard library’s policy is to use noexcept only on functions that must not throw or fail. Functions that are potentially throwing but do not actually throw exceptions (due to implementation) typically are not marked as noexcept. Use the noexcept specifier in specific cases where you want to express a no-fail or no-throw guarantee.

What is the difference between noexcept and noexcept (false)?

In contrast, noexcept (false) means that the function may throw an exception. The noexcept specification is part of the function type but can not be used for function overloading. There are two good reasons for the use of noexcept: First, an exception specifier documents the behaviour of the function.

What is the use of a noexcept specifier in Python?

It can be used in a noexcept specifier of a function template to declare that the function may throw exceptions depending on the current type. To make my description clear here is a simple example of a function template which copies it return value. Of course, the most interesting line in this example is the line (1).

What is the difference between throw () and noexcept in C++?

The noexcept specification is equivalent to the noexcept (true) specification. throw () is equivalent to noexcept (tru e) but was deprecated with C++11 and will be removed with C++20. In contrast, noexcept (false) means that the function may throw an exception.


Video Answer


2 Answers

Why is clang doing this extra code for us?

Because the behaviour of the function is different.

I didn't request any other action but calling new()

By declaring the function noexcept, you've requested std::terminate to be called in case an exception propagates out of the function.

allocated_int in the first program never calls std::terminate, while allocated_int in the second program may call std::terminate. Note that the amount of added code is much less if you remember to enable the optimiser. Comparing non-optimised assembly is mostly futile.

You can use non-throwing allocation to prevent that:

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

It's indeed an astute observation that doing potentially throwing things inside non-throwing function can introduce some extra work that wouldn't need to be done if the same things were done in a potentially throwing function.

I've been trying to lookup the advantages of disabling exceptions on certain functions

The advantage of using non-throwing is potentially realised where such function is called; not within the function itself.

like image 200
eerorika Avatar answered Oct 16 '22 10:10

eerorika


Without nothrow, your function just acts as a front end to the allocation function you call. It doesn't have any real behavior of its own. In fact, in a real executable, if you do link-time optimization there's a pretty good chance that it'll completely disappear.

When you add noexcept, your code is silently transformed into something roughly like this:

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

The extra code you see generated is what's needed to catch the exception and call terminate when/if needed.

like image 29
Jerry Coffin Avatar answered Oct 16 '22 10:10

Jerry Coffin