Forked from christianparpart/cxx-function-object-performance-test.cpp
Last active
April 18, 2026 22:24
-
-
Save Luiz-Monad/89477b326f95621851690b7bdfb25838 to your computer and use it in GitHub Desktop.
"Mini Benchmark" on showcasing the performance overhead of different function-pointer techniques, as provided by C++11.Keep in mind, these are *bare* calls to functions that do only one thing, atomically incrementing its counter; By checking the generated assembler output you may find out, that a bare C-function pointer loop is compiled into 3 C…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // clang++ -o tests/perftest-fncb tests/perftest-fncb.cpp -std=c++11 -O3 -march=native -mtune=native | |
| #include <algorithm> | |
| #include <atomic> | |
| #include <chrono> | |
| #include <functional> | |
| #include <stdio.h> | |
| #include <string> | |
| #include <thread> | |
| #include <vector> | |
| typedef unsigned long long counter_t; | |
| struct Counter { | |
| volatile counter_t c; | |
| Counter() : | |
| c(0) | |
| {} | |
| } counter; | |
| void bare(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| void ptr(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| void cxx(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| struct CXO1 { | |
| void om(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| virtual void omv(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| void cxo1(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| virtual void virt(Counter* counter) { __sync_fetch_and_add(&counter->c, 1); } | |
| } cxo1; | |
| void (*ptr_cb)(Counter*) = nullptr; | |
| std::function<void(Counter*)> cxx_cb; | |
| std::function<void(Counter*)> cxo1_cb; | |
| std::function<void(Counter*)> virt_cb; | |
| std::function<void(Counter*)> lambda_cb; | |
| std::atomic<bool> run; | |
| void bare_main() { while (run.load()) { bare(&counter); } } | |
| void om_main() { while (run.load()) { cxo1.om(&counter); } } | |
| void omv_main() { while (run.load()) { cxo1.omv(&counter); } } | |
| void ptr_main() { while (run.load()) { ptr_cb(&counter); } } | |
| void cxx_main() { while (run.load()) { cxx_cb(&counter); } } | |
| void cxo1_main() { while (run.load()) { cxo1_cb(&counter); } } | |
| void virt_main() { while (run.load()) { virt_cb(&counter); } } | |
| void lambda_main() { while (run.load()) { lambda_cb(&counter); } } | |
| static const char* labels[] = { | |
| "bare function", | |
| "object method", | |
| "object method (virtual)", | |
| "function pointer", | |
| "C++11 std::function -> bare function", | |
| "C++11 std::function -> object method", | |
| "C++11 std::function -> object method (virtual)", | |
| "C++11 std::function -> lambda", | |
| }; | |
| struct Sample { | |
| counter_t delta; | |
| counter_t total; | |
| size_t label; | |
| }; | |
| int main() | |
| { | |
| ptr_cb = &ptr; | |
| cxx_cb = std::bind(&cxx, std::placeholders::_1); | |
| cxo1_cb = std::bind(&CXO1::cxo1, &cxo1, std::placeholders::_1); | |
| virt_cb = std::bind(&CXO1::virt, &cxo1, std::placeholders::_1); | |
| lambda_cb = [](Counter* c) { __sync_fetch_and_add(&c->c, 1); }; | |
| static const size_t NUM_COLS = 8; | |
| static const size_t NUM_ROWS = 60; | |
| std::vector<std::vector<Sample>> cols; | |
| cols.reserve(NUM_COLS); | |
| for (size_t test = 0; test < NUM_COLS; ++test) { | |
| counter.c = 0; | |
| cols.push_back({}); | |
| auto& rows = cols.back(); | |
| rows.reserve(NUM_ROWS); | |
| run.store(true); | |
| std::thread t; | |
| switch (test) { | |
| case 0: t = std::thread(bare_main); break; | |
| case 1: t = std::thread(om_main); break; | |
| case 2: t = std::thread(omv_main); break; | |
| case 3: t = std::thread(ptr_main); break; | |
| case 4: t = std::thread(cxx_main); break; | |
| case 5: t = std::thread(cxo1_main); break; | |
| case 6: t = std::thread(virt_main); break; | |
| case 7: t = std::thread(lambda_main); break; | |
| } | |
| counter_t prev = 0; | |
| for (size_t n = 0; n < NUM_ROWS; ++n) { | |
| std::this_thread::sleep_for(std::chrono::seconds(1)); | |
| counter_t cur = counter.c; | |
| rows.push_back({ cur - prev, cur, test }); | |
| prev = cur; | |
| } | |
| run.store(false); | |
| t.join(); | |
| } | |
| for (size_t n = 0; n < NUM_ROWS; ++n) { | |
| std::vector<Sample> row; | |
| row.reserve(NUM_COLS); | |
| for (size_t col = 0; col < NUM_COLS; ++col) | |
| row.push_back(cols[col][n]); | |
| std::sort(row.begin(), row.end(), | |
| [](const Sample& a, const Sample& b) { | |
| return a.delta > b.delta; | |
| }); | |
| printf("second %zu\n", n + 1); | |
| printf("===========\n"); | |
| printf(" %16s %16s %s\n", "calls/sec", "total", "method"); | |
| printf(" %16s %16s %s\n", "---------", "-----", "------"); | |
| for (const auto& s : row) | |
| printf(" %16llu %16llu %s\n", s.delta, s.total, labels[s.label]); | |
| putchar('\n'); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment