├── .projectile ├── .gitignore ├── .clang-format ├── .dir-locals.el ├── CMakeLists.txt ├── README.md ├── LICENSE ├── example.cpp ├── benchmark.cpp └── Function.h /.projectile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((c++-mode (eval add-hook 'before-save-hook #'clang-format-buffer nil t)) 2 | (nil . ((compile-command . "cmake --build build"))) 3 | ) 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(Function) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-devirtualize") 5 | 6 | add_executable(example example.cpp) 7 | add_executable(benchmark benchmark.cpp) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Function.h 2 | 3 | Heap allocation free version of C++11 *std::function*. 4 | 5 | Function.h stores the closure in an internal buffer instead of heap 6 | allocated memory. This is useful for low latency agent and thread pool 7 | systems. Please note that the captured values can perfom allocations, 8 | for example *std::string*. 9 | 10 | ## Benchmark 11 | 12 | *Function.h* is quite a lot faster than *std::function* to 13 | construct. Invocation overhead is the same for both. 14 | 15 | Compiled with `gcc -O3 -fno-devirtualize` (gcc 5.2). 16 | 17 | ``` 18 | construction overhead 19 | std::function: 42ns/op 20 | Function: 4ns/op 21 | 22 | invokation overhead: 23 | std::function: 2ns/op 24 | Function: 2ns/op 25 | virtual: 2ns/op 26 | ``` 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Erik Rigtorp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include "Function.h" 2 | #include 3 | 4 | int function(std::ostream& os) 5 | { 6 | os << "Free function\n"; 7 | return 0; 8 | } 9 | 10 | struct Class 11 | { 12 | int operator()(std::ostream& os) 13 | { 14 | os << "Class::operator()\n"; 15 | return 1; 16 | } 17 | 18 | int fun(std::ostream& os) 19 | { 20 | os << "Class::fun()\n"; 21 | return 2; 22 | } 23 | }; 24 | 25 | int main(int argc, char *argv[]) { 26 | int foo = 1; 27 | double bar = 3; 28 | 29 | Function f([=](std::ostream &os) { 30 | os << "test " << foo << " " << bar << std::endl; 31 | return foo; 32 | }); 33 | f(std::cout); 34 | 35 | f = &function; 36 | f(std::cout); 37 | 38 | f = function; 39 | f(std::cout); 40 | 41 | Class cl; 42 | f = std::bind(&Class::fun, &cl, std::placeholders::_1); 43 | f(std::cout); 44 | 45 | f = cl; 46 | f(std::cout); 47 | 48 | f = nullptr; 49 | try { f(std::cout); } 50 | catch (std::bad_function_call const&) { } 51 | std::cout << "Assigned nullptr, function is " << (f? "not " : "") << "empty\n"; 52 | 53 | std::cout << "Size of Function: " << sizeof(f) << '\n'; 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "Function.h" 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | constexpr size_t count = 100000000; 7 | 8 | using namespace std::chrono; 9 | 10 | volatile size_t state = 0; 11 | struct State { 12 | size_t a, b, c; 13 | }; 14 | State state2 = {0, 0, 0}; 15 | 16 | std::cout << "construction overhead" << std::endl; 17 | 18 | { 19 | auto start = high_resolution_clock::now(); 20 | for (size_t i = 0; i < count; ++i) { 21 | std::function stdfun = [&state, state2, i]() { state = i; }; 22 | stdfun(); 23 | } 24 | auto stop = high_resolution_clock::now(); 25 | auto duration = stop - start; 26 | std::cout << "std::function: " 27 | << duration_cast(duration).count() / count << "ns/op" 28 | << std::endl; 29 | } 30 | 31 | { 32 | auto start = high_resolution_clock::now(); 33 | for (size_t i = 0; i < count; ++i) { 34 | Function fun = [&state, state2, i]() { state = i; }; 35 | fun(); 36 | } 37 | auto stop = high_resolution_clock::now(); 38 | auto duration = stop - start; 39 | std::cout << "Function: " 40 | << duration_cast(duration).count() / count << "ns/op" 41 | << std::endl; 42 | } 43 | 44 | std::cout << "invokation overhead" << std::endl; 45 | 46 | { 47 | std::function stdfun([&state](size_t i) { state = i; }); 48 | auto start = high_resolution_clock::now(); 49 | for (size_t i = 0; i < count; ++i) { 50 | stdfun(i); 51 | } 52 | auto stop = high_resolution_clock::now(); 53 | auto duration = stop - start; 54 | std::cout << "std::function: " 55 | << duration_cast(duration).count() / count << "ns/op" 56 | << std::endl; 57 | } 58 | 59 | { 60 | Function fun([&state](size_t i) { state = i; }); 61 | auto start = high_resolution_clock::now(); 62 | for (size_t i = 0; i < count; ++i) { 63 | fun(i); 64 | } 65 | auto stop = high_resolution_clock::now(); 66 | auto duration = stop - start; 67 | std::cout << "Function: " 68 | << duration_cast(duration).count() / count << "ns/op" 69 | << std::endl; 70 | } 71 | 72 | { 73 | // Must compile this with -fno-devirtualize to prevent inlining 74 | struct Fun { 75 | virtual void fun(size_t) = 0; 76 | }; 77 | struct Impl : public Fun { 78 | void fun(size_t i) override { state = i; }; 79 | volatile size_t state = 0; 80 | }; 81 | Impl impl; 82 | auto start = high_resolution_clock::now(); 83 | for (size_t i = 0; i < count; ++i) { 84 | Fun *fun = &impl; 85 | fun->fun(i); 86 | } 87 | auto stop = high_resolution_clock::now(); 88 | auto duration = stop - start; 89 | std::cout << "virtual: " 90 | << duration_cast(duration).count() / count << "ns/op" 91 | << std::endl; 92 | } 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /Function.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Erik Rigtorp 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | 30 | template class Function; 31 | 32 | template class Function { 33 | public: 34 | Function() noexcept {} 35 | 36 | Function(std::nullptr_t) noexcept {} 37 | 38 | Function(const Function &other) { 39 | if (other) { 40 | other.manager(data, other.data, Operation::Clone); 41 | invoker = other.invoker; 42 | manager = other.manager; 43 | } 44 | } 45 | 46 | Function(Function &&other) { other.swap(*this); } 47 | 48 | template Function(F &&f) { 49 | using f_type = typename std::decay::type; 50 | static_assert(alignof(f_type) <= alignof(Storage), "invalid alignment"); 51 | static_assert(sizeof(f_type) <= sizeof(Storage), "storage too small"); 52 | new (&data) f_type(std::forward(f)); 53 | invoker = &invoke; 54 | manager = &manage; 55 | } 56 | 57 | ~Function() { 58 | if (manager) { 59 | manager(&data, nullptr, Operation::Destroy); 60 | } 61 | } 62 | 63 | Function &operator=(const Function &other) { 64 | Function(other).swap(*this); 65 | return *this; 66 | } 67 | 68 | Function &operator=(Function &&other) { 69 | Function(std::move(other)).swap(*this); 70 | return *this; 71 | } 72 | 73 | Function &operator=(std::nullptr_t) { 74 | if (manager) { 75 | manager(&data, nullptr, Operation::Destroy); 76 | manager = nullptr; 77 | invoker = nullptr; 78 | } 79 | return *this; 80 | } 81 | 82 | template Function &operator=(F &&f) { 83 | Function(std::forward(f)).swap(*this); 84 | return *this; 85 | } 86 | 87 | template Function &operator=(std::reference_wrapper f) { 88 | Function(f).swap(*this); 89 | return *this; 90 | } 91 | 92 | void swap(Function &other) { 93 | std::swap(data, other.data); 94 | std::swap(manager, other.manager); 95 | std::swap(invoker, other.invoker); 96 | } 97 | 98 | explicit operator bool() const noexcept { return !!manager; } 99 | 100 | R operator()(Args... args) { 101 | if (!invoker) { 102 | throw std::bad_function_call(); 103 | } 104 | return invoker(&data, std::forward(args)...); 105 | } 106 | 107 | private: 108 | enum class Operation { Clone, Destroy }; 109 | 110 | using Invoker = R (*)(void *, Args &&...); 111 | using Manager = void (*)(void *, void *, Operation); 112 | using Storage = typename std::aligned_storage::type; 113 | 114 | template 115 | static R invoke(void *data, Args &&... args) { 116 | F &f = *static_cast(data); 117 | return f(std::forward(args)...); 118 | } 119 | 120 | template 121 | static void manage(void *dest, void *src, Operation op) { 122 | switch (op) { 123 | case Operation::Clone: 124 | new (dest) F(*static_cast(src)); 125 | break; 126 | case Operation::Destroy: 127 | static_cast(dest)->~F(); 128 | break; 129 | } 130 | } 131 | 132 | Storage data; 133 | Invoker invoker = nullptr; 134 | Manager manager = nullptr; 135 | }; 136 | --------------------------------------------------------------------------------