Function pointer vs. std::function vs Function template

Showcase

Code ▼
#include <chrono>
#include <functional>
#include <iostream>

// Function pointer
void func_1(void (*f)()) { f(); }

// std::function
void func_2(const std::function<void()>& f) { f(); }

// Function template
void func_3(std::regular_invocable auto&& f) { f(); }
// Before C++20 it had to be written like this:
// template <typename T> concept Callable = requires(T&& t) { { t() }; };
// template <Callable F> void func_3(F&& f) { f(); }

int main()
{
    using namespace std::chrono;

    {
        auto start = high_resolution_clock::now();
        void (*f)() = [](){};
        for (auto i = 0; i < 10000000; ++i) { func_1(f); }
        auto stop = high_resolution_clock::now();

        auto ms = duration_cast<milliseconds>(stop - start);
        std::cout << "Function pointer: Time="
                  << ms.count() << "ms" << std::endl;
    }
    {
        auto start = high_resolution_clock::now();
        std::function<void()> f = [](){};
        for (auto i = 0; i < 10000000; ++i) { func_2(f); }
        auto stop = high_resolution_clock::now();

        auto ms = duration_cast<milliseconds>(stop - start);
        std::cout << "std::function: Time="
                  << ms.count() << "ms" << std::endl;
    }
    {
        auto start = high_resolution_clock::now();
        auto f = [](){};
        for (auto i = 0; i < 10000000; ++i) { func_3(f); }
        auto stop = high_resolution_clock::now();

        auto ms = duration_cast<milliseconds>(stop - start);
        std::cout << "Function template: Time="
                  << ms.count() << "ms" << std::endl;
    }

    return 0;
}
Compiler ▼

– Tool: godbolt.org
– Compiler: x86-64 Clang 16.0.0
– Args: -std=c++20
– Output: Intel asm syntax and Demangle identifiers

Result ▼
Function pointer: Time=30ms
std::function: Time=102ms
Function template: Time=17ms

Analysis

Function Pointer ▼
        mov     dword ptr [rbp - 36], 0
.LBB4_1:
        cmp     dword ptr [rbp - 36], 10000000
        jge     .LBB4_4
        mov     rdi, qword ptr [rbp - 24]
        call    func_1(void (*)())
        mov     eax, dword ptr [rbp - 36]
        add     eax, 1
        mov     dword ptr [rbp - 36], eax
        jmp     .LBB4_1

func_1(void (*)()):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     qword ptr [rbp - 8], rdi
        call    qword ptr [rbp - 8]
        add     rsp, 16
        pop     rbp
        ret

When calling f() we have to go throw one indirection:
–> “call qword ptr [rbp – 8]” (Dereferencing the function pointer.)

std::function ▼
        mov     dword ptr [rbp - 116], 0
.LBB4_5:
        cmp     dword ptr [rbp - 116], 10000000
        jge     .LBB4_10
        lea     rdi, [rbp - 104]
        call    func_2(std::function<void ()> const&)
        jmp     .LBB4_7
.LBB4_7:
        jmp     .LBB4_8
.LBB4_8:
        mov     eax, dword ptr [rbp - 116]
        add     eax, 1
        mov     dword ptr [rbp - 116], eax
        jmp     .LBB4_5

func_2(std::function<void ()> const&):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     qword ptr [rbp - 8], rdi
        mov     rdi, qword ptr [rbp - 8]
        call    std::function<void ()>::operator()() const
        add     rsp, 16
        pop     rbp
        ret

When calling f() we have to go throw the std::function call logic:
–> “call std::function::operator()() const”

Function template ▼
        mov     dword ptr [rbp - 180], 0
.LBB4_18:
        cmp     dword ptr [rbp - 180], 10000000
        jge     .LBB4_21
        lea     rdi, [rbp - 176]
        call    void func_3<main::$_1&>(main::$_1&)
        mov     eax, dword ptr [rbp - 180]
        add     eax, 1
        mov     dword ptr [rbp - 180], eax
        jmp     .LBB4_18

void func_3<main::$_1&>(main::$_1&):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     qword ptr [rbp - 8], rdi
        mov     rdi, qword ptr [rbp - 8]
        call    main::$_1::operator()() const
        add     rsp, 16
        pop     rbp
        ret

When calling f() we have to go throw zero indirections:
–> “call main::$_1::operator()() const”


Disclaimer: This code has been compiled without optimization!