diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f0c2ac..dcabe28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(embedded_function VERSION 2.1.5 LANGUAGES CXX) +project(embedded_function VERSION 2.1.6 LANGUAGES CXX) add_library(embedded_function INTERFACE) add_library(embedded_function::functions ALIAS embedded_function) set_target_properties(embedded_function PROPERTIES EXPORT_NAME functions) diff --git a/README.md b/README.md index fd0179d..3ca023b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Embedded Function

- Version - 2.1.5 + Version - 2.1.6 License - MIT C++ - 11/14/17/20/23

@@ -21,9 +21,11 @@ ## 📌 Overview -*Embedded Function* is a **lightweight** and **no-heap-allocation** function wrapper collection implemented based on the C++11 standard, tailored specifically for embedded systems. +*Embedded Function* is a **lightweight** and **no-heap-allocation** function wrapper collection implemented based on the C++11 standard, optimized([see below](#-performance-optimization)) for resource-constrained or high-performance environments. -In only **one** [header file](./include/embed/embed_function.hpp), **4** function wrappers are provided as follows: +The library is [freestanding](https://en.cppreference.com/w/cpp/freestanding), making it feasible for embedded development or kernel design of an operating system. + +In a [single header file](./include/embed/embed_function.hpp), **four** function wrappers are provided as follows: ```cpp namespace ebd { @@ -76,18 +78,28 @@ auto main() -> int { ```cpp /// The definition of method of a function wrapper is as follows: -ebd::fn fn_; -// ^ ^ ^ ^ ^ ^ -// | | | | | | -// Return type | | | | | -// Parameters ~|~~~~~|~~~~~| | | -// Qualifier ~~~~~~~~~~~~~~~~~~~~| | -// Buffer size ~~~~~~~~~~~~~~~~~~~~~~~~~~~| + FnWrapper fn_ = +[](int, char) {}; +// ^ ^ ^~~~~~~ ^ ^~~~~~ ^~~~~~~~~~~~~ +// | | | | | | +// Function wrapper | | | | | +// Return type ~~~~~| | | | | +// Parameters ~~~~~~~~~~| | | | +// Qualifier ~~~~~~~~~~~~~~~~~~~~~~~~| | | +// Buffer size ~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | +// Callable object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ``` -> The *`Qualifier`* is used to restrict the callable objects wrapped within `ebd::fn`, rather than `ebd::fn` itself. In other words, the `operator()` of the `ebd::fn` object will be qualified with the `Qualifier` modifier. +- *`Function wrapper`*: One of `ebd::fn`, `ebd::unique_fn`, `ebd::safe_fn` and `ebd::fn_ref`. + +- *`Return type`*: A type that can be implicitly converted from the direct return type of *`Callable object`*. + +- *`Parameters`*: Types that can implicitly converts to the parameter types of *`Callable object`*. -> The *`Buffer size`* is the size used to store the callable object, which can be omitted. If omitted, this parameter will be set to `detail::default_buffer_size::value` by default, which is sufficient to store most common callable objects, including function pointers, simple non-capturing and capturing lambdas, and lightweight custom classes. +- *`Qualifier`*: Applies to the wrapper's `operator()` (e.g., `const`, `noexcept`, `&`, `&&`), restricting which callable objects can be stored. + +- *`Buffer size`*: Size (in bytes) of the internal storage. Defaults to `DefaultSize`. Triggers `static_assert` if insufficient - no heap allocation. + +- *`Callable object`*: Any entity callable with the target signature (function pointer, lambda, function object, `std::reference_wrapper`). Copied or moved into the buffer depending on wrapper type. ## 🧠 Design goals driving the design @@ -138,25 +150,19 @@ ebd::fn fn_; 4. **Triviality**: `fn_ref` is trivially copyable (same as `std::function_ref`). -## 🚀 Performance optimization - -### Branch elimination - -`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` completely eliminate runtime checks for empty function states during invocation, significantly boosting performance of frequent function calls. - -### Smart forwarding - -`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` enable scalar arguments and small-sized trivial arguments to be passed via registers instead of having to be passed via the stack as in `std::function`. This significantly reduces the memory access overhead during parameter passing. - -### Zero-stack overhead - -`ebd::fn_ref` occupies no stack space when used as a function parameter; it is passed entirely in registers. This allows the compiler to directly tail-call the wrapped target, removing the cost of an extra stack frame. See [x86_64-asm](./docs/perf/x86_64_gcc_fn_ref_zero_stack.md). - -### Stateless elimination +### Convertibility -`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` do not store the functor or its pointer if the functor is stateless (e.g., empty classes with trivial operations). This reduces memory access operations and improves cache efficiency. +- `Yes-D`: Convertible and direct wrapping (`To.BufferSize` >= `From.BufferSize`); +- `Yes-I`: Convertible and indirect wrapping (`To.BufferSize` >= `sizeof(From)`); +- `Yes-R`: Convertible and non-owning wrapping. +- `No`: Inconvertible -> Click [x64-asm](./docs/perf/x86_64_msvc_asm_analysis.md), [rv32-asm](./docs/perf/riscv_gcc_asm_analysis.md) and [arm32-asm](./docs/perf/arm_gcc_asm_analysis.md) to see more details. +| From \ To | `ebd::fn` | `ebd::unique_fn` | `ebd::safe_fn` | `ebd::fn_ref` | +| :---: | :---: | :---: | :---: | :---: | +| `ebd::fn` | Yes-D | Yes-D | No | Yes-R | +| `ebd::unique_fn` | No | Yes-D | No | Yes-R | +| `ebd::safe_fn` | Yes-I | Yes-I | Yes-D | Yes-R | +| `ebd::fn_ref` | Yes-I | Yes-I | Yes-I | Yes-D | ## 🧩 Automatic deduction @@ -193,11 +199,11 @@ auto f = ebd::make_fn(Ambiguous_Callable_Object); ```cpp // Create specified function wrapper and automatically deduce the template arguments. -// The Callable_Object should be unambiguously callable (non-overload). -auto f = ebd::make_fn(Callable_Object); -auto f = ebd::make_fn(Callable_Object); -auto f = ebd::make_fn(Callable_Object); -auto f = ebd::make_fn(Callable_Object); +// The Callable_Object should be unambiguously callable (non-overload) if `Signature` is omitted. +auto f = ebd::make_fn(Callable_Object); +auto f = ebd::make_fn(Callable_Object); +auto f = ebd::make_fn(Callable_Object); +auto f = ebd::make_fn(Callable_Object); ``` ```cpp @@ -291,73 +297,53 @@ Every compiler with modern C++11 support should work. Go to the `/test/` directory, and follow the instructions in [`HOW-TO-TEST.md`](./test/HOW-TO-TEST.md) to run the tests. -## ⏱️ Benchmark +## 🚀 Performance optimization -Go to the `/benchmark/` directory, and follow the instructions in [`HOW-TO-BENCHMARK.md`](./benchmark/HOW-TO-BENCHMARK.md) to run the tests. +### Branch elimination -> *( Compiler: `MSVC` Standard: `C++14` Config: `Release` Tool: [picobench](https://github.com/iboB/picobench) )* +`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` completely eliminate runtime checks for empty function states during invocation, significantly boosting performance of frequent function calls. -> **std**: `std::function`, **ebd**: `ebd::fn`, **fu2**: [`fu2::function`](https://github.com/Naios/function2) +### Smart forwarding -```md -## FreeFunction.ScalarParameters: +`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` enable scalar arguments and small-sized trivial arguments to be passed via registers instead of having to be passed via the stack as in `std::function`. This significantly reduces the memory access overhead during parameter passing. - Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second ---------------------------|--------:|----------:|--------:|-------:|----------: - free_scalar_std * | 10000 | 0.030 | 3 | - |332225913.6 - free_scalar_ebd | 10000 | 0.028 | 2 | 0.930 |357142857.1 - free_scalar_fu2 | 10000 | 0.052 | 5 | 1.731 |191938579.7 - free_scalar_std * | 100000 | 0.301 | 3 | - |332667997.3 - free_scalar_ebd | 100000 | 0.265 | 2 | 0.881 |377643504.5 - free_scalar_fu2 | 100000 | 0.523 | 5 | 1.742 |191021967.5 - free_scalar_std * | 1000000 | 3.006 | 3 | - |332712270.4 - free_scalar_ebd | 1000000 | 2.708 | 2 | 0.901 |369317132.6 - free_scalar_fu2 | 1000000 | 5.264 | 5 | 1.751 |189958778.9 - -## FreeFunction.TrivialParameters: +### Zero-stack overhead - Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second ---------------------------|--------:|----------:|--------:|-------:|----------: - free_trivial_std * | 10000 | 0.032 | 3 | - |311526479.8 - free_trivial_ebd | 10000 | 0.024 | 2 | 0.754 |413223140.5 - free_trivial_fu2 | 10000 | 0.052 | 5 | 1.626 |191570881.2 - free_trivial_std * | 100000 | 0.322 | 3 | - |310366232.2 - free_trivial_ebd | 100000 | 0.240 | 2 | 0.746 |415800415.8 - free_trivial_fu2 | 100000 | 0.510 | 5 | 1.583 |196001568.0 - free_trivial_std * | 1000000 | 3.222 | 3 | - |310375865.2 - free_trivial_ebd | 1000000 | 2.508 | 2 | 0.778 |398692289.3 - free_trivial_fu2 | 1000000 | 5.792 | 5 | 1.798 |172660876.8 - -## FreeFunction.CopyHardParameters: +`ebd::fn_ref` occupies no stack space when used as a function parameter; it is passed entirely in registers. This allows the compiler to directly tail-call the wrapped target, removing the cost of an extra stack frame. See [x86_64-asm](./docs/perf/x86_64_gcc_fn_ref_zero_stack.md). - Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second ---------------------------|--------:|----------:|--------:|-------:|----------: - free_copyhard_std * | 10000 | 0.197 | 19 | - | 50684237.2 - free_copyhard_ebd | 10000 | 0.198 | 19 | 1.004 | 50505050.5 - free_copyhard_fu2 | 10000 | 0.303 | 30 | 1.537 | 32981530.3 - free_copyhard_std * | 100000 | 1.976 | 19 | - | 50604726.5 - free_copyhard_ebd | 100000 | 1.982 | 19 | 1.003 | 50456632.5 - free_copyhard_fu2 | 100000 | 3.044 | 30 | 1.541 | 32849352.9 - free_copyhard_std * | 1000000 | 19.898 | 19 | - | 50256307.2 - free_copyhard_ebd | 1000000 | 20.052 | 20 | 1.008 | 49870088.4 - free_copyhard_fu2 | 1000000 | 31.358 | 31 | 1.576 | 31889890.6 - -## FreeFunction.CallTrivialParameters: +### Stateless elimination + +`ebd::fn` / `ebd::unique_fn` / `ebd::safe_fn` / `ebd::fn_ref` do not store the functor or its pointer if the functor is stateless (e.g., empty classes with trivial operations). This reduces memory access operations and improves cache efficiency. + +> Click [x64-asm](./docs/perf/x86_64_msvc_asm_analysis.md), [rv32-asm](./docs/perf/riscv_gcc_asm_analysis.md) and [arm32-asm](./docs/perf/arm_gcc_asm_analysis.md) to see more details. + +## ⏱️ Benchmark + +**Embedded-Function has 5%~30% performance enhancement over `std::function`.** + +> *( `Compiler`: GCC-14 `Standard`: C++14 `Config`: -Os `Tool`: [picobench](https://github.com/iboB/picobench) `fu2`: [function2](https://github.com/Naios/function2) )* + +### StdOperatorWrapper.FunctionWrapperAsParams: Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second --------------------------|--------:|----------:|--------:|-------:|----------: - free_calltrivial_std * | 10000 | 0.032 | 3 | - |311526479.8 - free_calltrivial_ebd | 10000 | 0.024 | 2 | 0.751 |414937759.3 - free_calltrivial_fu2 | 10000 | 0.056 | 5 | 1.757 |177304964.5 - free_calltrivial_std * | 100000 | 0.320 | 3 | - |312597686.8 - free_calltrivial_ebd | 100000 | 0.257 | 2 | 0.802 |389711613.4 - free_calltrivial_fu2 | 100000 | 0.584 | 5 | 1.827 |171115674.2 - free_calltrivial_std * | 1000000 | 3.223 | 3 | - |310269934.8 - free_calltrivial_ebd | 1000000 | 2.407 | 2 | 0.747 |415506710.4 - free_calltrivial_fu2 | 1000000 | 5.934 | 5 | 1.841 |168517551.1 -``` - -> See [here](https://github.com/Kim-J-Smith/Embedded-Function/actions/workflows/benchmark.yml) for more benchmark results. + `std::function` * | 10000 | 0.090 | 8 | - |111671952.5 + `fu2::function` | 10000 | 0.176 | 17 | 1.968 | 56744349.7 + **`ebd::fn`** | 10000 | 0.068 | 6 | 0.758 |147412179.2 + `fu2::function_view` | 10000 | 0.034 | 3 | 0.379 |294602875.3 + **`ebd::fn_ref`** | 10000 | 0.034 | 3 | 0.375 |297424305.5 + `std::function` * | 100000 | 0.895 | 8 | - |111756442.5 + `fu2::function` | 100000 | 1.765 | 17 | 1.973 | 56644386.5 + **`ebd::fn`** | 100000 | 0.678 | 6 | 0.758 |147444347.1 + `fu2::function_view` | 100000 | 0.340 | 3 | 0.380 |294061429.4 + **`ebd::fn_ref`** | 100000 | 0.308 | 3 | 0.345 |324361494.4 + `std::function` * | 1000000 | 9.952 | 9 | - |100481295.4 + `fu2::function` | 1000000 | 17.733 | 17 | 1.782 | 56391833.9 + **`ebd::fn`** | 1000000 | 6.832 | 6 | 0.686 |146378186.5 + `fu2::function_view` | 1000000 | 3.420 | 3 | 0.344 |292392274.6 + **`ebd::fn_ref`** | 1000000 | 3.249 | 3 | 0.326 |307826614.8 + +> See [here](https://github.com/Kim-J-Smith/Embedded-Function/actions/workflows/benchmark.yml) for more benchmark results. Follow [`HOW-TO-BENCHMARK.md`](./benchmark/HOW-TO-BENCHMARK.md) to run the benchmark in your platform. ## 🧭 Future learning & evolution reference diff --git a/docs/api/make_fn.md b/docs/api/make_fn.md index aee405f..32d9796 100644 --- a/docs/api/make_fn.md +++ b/docs/api/make_fn.md @@ -2,152 +2,187 @@ ## Overview -`ebd::make_fn` is a factory function that creates function wrapper objects. It automatically deduces the signature and buffer size of the callable object, and returns an appropriate function wrapper (`ebd::fn` or `ebd::unique_fn`). +`ebd::make_fn` is a factory for creating Embedded Function wrappers from callable objects, function pointers, member pointers, and existing wrappers. + +It can: + +- deduce the signature of a lambda or other uniquely callable functor, +- choose `ebd::fn` or `ebd::unique_fn` automatically, +- build an empty wrapper with an explicit signature, +- create a wrapper with a specific wrapper type such as `ebd::safe_fn` or `ebd::fn_ref`. ## Overloads -### 1. Make function with specified signature for copyable functor +### 1. Copyable functor with explicit signature ```cpp -template -EMBED_NODISCARD inline fn -make_fn(Functor&& functor) noexcept; +template , + std::size_t BufferSize = sizeof(Class), + typename Fn = detail::conditional_t< + std::is_copy_constructible::value, + fn, + unique_fn>, + bool NoThrow = detail::is_nothrow_construct_from_functor::value> +EMBED_NODISCARD inline Fn make_fn(Functor&& functor) noexcept(NoThrow); ``` -Creates an `ebd::fn` with the specified signature for a copyable functor. +Creates a wrapper for a class-type callable when the signature is specified explicitly. For copyable functors, the resulting type is `ebd::fn`. -### 2. Make function with specified signature for move-only functor +### 2. Move-only functor with explicit signature ```cpp -template +template ::value> EMBED_NODISCARD inline unique_fn -make_fn(Functor&& functor) noexcept; +make_fn(Functor&& functor) noexcept(NoThrow); ``` -Creates an `ebd::unique_fn` with the specified signature for a move-only functor. +Creates an `ebd::unique_fn` for a move-only functor when the signature is specified explicitly. -### 3. Make an empty function with specified signature and buffer size +### 3. Empty wrapper with explicit signature ```cpp -template +template EMBED_NODISCARD inline fn make_fn(std::nullptr_t = nullptr) noexcept; ``` -Creates an empty `ebd::fn` with the specified signature and buffer size. +Creates an empty `ebd::fn` with the given signature and buffer size. -### 4. Make function for function pointer (auto deduce signature and buffer size) +### 4. Function pointer with deduced signature ```cpp template EMBED_NODISCARD inline fn -make_fn(Ret (*func_ptr) (Args...)) noexcept; +make_fn(Ret (*func_ptr)(Args...)) noexcept; ``` -Creates an `ebd::fn` from a function pointer, automatically deducing the signature and buffer size. +Creates an `ebd::fn` from a free-function pointer and deduces both signature and buffer size. -### 5. Make function for function pointer with specified signature +### 5. Function pointer with explicit signature ```cpp -template ::pure_sig*> +template ::pure_sig*> EMBED_NODISCARD inline fn make_fn(FunctionPtr func_ptr) noexcept; ``` -Creates an `ebd::fn` from a function pointer with the specified signature. +Creates an `ebd::fn` from a free-function pointer using the specified signature. -### 6. Create a function from another function (Copy) +### 6. Copy from another wrapper ```cpp template EMBED_NODISCARD inline detail::function -make_fn(const detail::function& fn) noexcept; +make_fn(const detail::function& fn) +noexcept(Cfg::isView || Cfg::assertNoThrow); ``` -Creates a function wrapper by copying another function wrapper. +Creates a wrapper by copying another `ebd::detail::function`. -### 7. Create a function from another function (Move) +### 7. Move from another wrapper ```cpp template EMBED_NODISCARD inline detail::function -make_fn(detail::function&& fn) noexcept; +make_fn(detail::function&& fn) +noexcept(Cfg::isView || Cfg::assertNoThrow); ``` -Creates a function wrapper by moving another function wrapper. +Creates a wrapper by moving another `ebd::detail::function`. -### 8. Make a function from lambda or unique-operator() functor +### 8. Lambda or uniquely callable functor ```cpp -template , +template , std::size_t BufferSize = sizeof(Class), typename Signature = detail::get_unique_signature_t, typename Fn = detail::conditional_t< std::is_copy_constructible::value, - fn, unique_fn - >> -EMBED_NODISCARD inline Fn make_fn(Lambda&& fn) noexcept; + fn, + unique_fn>, + bool NoThrow = detail::is_nothrow_construct_from_functor::value> +EMBED_NODISCARD inline Fn make_fn(Lambda&& fn) noexcept(NoThrow); ``` -Creates a function wrapper from a lambda or functor with a unique `operator()`, automatically deducing the signature and buffer size. +Creates a wrapper from a lambda or other functor with exactly one viable `operator()`. The signature is deduced automatically. -### 9. Make function for pointer to member function +### 9. Pointer to member function ```cpp template EMBED_NODISCARD inline auto make_fn(Ret(Class::* memfunc)(Args...) C V REF NOEXCEPT) noexcept --> fn, Args...) const, sizeof(memfunc)>; +-> fn< + Ret(detail::get_qualified_with_t, Args...) const, + sizeof(memfunc) + >; ``` -Creates an `ebd::fn` from a pointer to member function, automatically deducing the signature and buffer size. +Creates an `ebd::fn` from a pointer to member function. Cv/ref/noexcept qualifiers are preserved in the generated signature family. -### 10. Make function for member function pointer with specified signature +### 10. Member function pointer with explicit signature ```cpp -template , +template , std::size_t BufferSize = sizeof(MemFuncPtr)> EMBED_NODISCARD inline fn make_fn(MemFuncPtr memfunc_ptr) noexcept; ``` -Creates an `ebd::fn` from a member function pointer with the specified signature. +Creates an `ebd::fn` from a pointer to member function using the specified signature. -### 11. Make function for pointer to member object +### 11. Pointer to member object ```cpp -template +template ::type> EMBED_NODISCARD inline auto make_fn(T Class::* ptr_memobj) noexcept --> fn; +-> fn; ``` -Creates an `ebd::fn` from a pointer to member object. +Creates an `ebd::fn` that reads a member object from an instance. -### 12. In-place make function (C++17+) +### 12. In-place construction (C++17+) ```cpp template -EMBED_NODISCARD inline auto make_fn(std::in_place_type_t, CArgs&&... args) noexcept; +EMBED_NODISCARD inline auto +make_fn(std::in_place_type_t, CArgs&&... args) +noexcept(std::is_nothrow_constructible::value); template EMBED_NODISCARD inline auto -make_fn(std::in_place_type_t, std::initializer_list il, CArgs&&... args) noexcept; +make_fn(std::in_place_type_t, std::initializer_list il, CArgs&&... args) +noexcept(std::is_nothrow_constructible&, CArgs...>::value); ``` -Creates a function wrapper by in-place constructing the callable object within the internal buffer. +Constructs the callable directly inside the wrapper buffer. The returned wrapper type is deduced from the functor. -### 13. Make function with specified wrapper +### 13. Explicit wrapper type ```cpp -template