patterncppMinor
No-copy cache line aligned function storage object with small object optimization
Viewed 0 times
linewithfunctionstoragealignedoptimizationcachesmallobjectcopy
Problem
I have implemented a
Also,
To use it you make a map or deque to construct in place or move construct into place. You can then use the
Note that it actually allows a lot of different types of lambdas to be stored in the same container (
Questions:
```
#include
#include
#include
#include
#include
#include
#include
#include
#include
// totalSize is the size of the entire object, not the payload
// the actual payload size is less than that by sizeof pointer
template
class CallableStorage
{
public:
static constexpr std::size_t smallSize = totalSize -
sizeof(std::size_
std::function-like class, - that is optimized for storing lambdas,
- that never copies the stored function,
- that has a small buffer to store small functors, avoiding dynamic memory allocation.
- is designed to be sized to exact multiples of a cache line, because the functors will probably be accessed from different cores.
- stores the size of the stored object to be able to decide whether to treat it as a small-object or large-object.
- allows one storage container to store a lot of different types of callable objects
Also,
- Objects that are too big automatically fall back to a
new, and only a pointer is stored.
- Bare function pointers are supported, stored in the small object buffer
- It does not use vtbl for
operator()codepath. Calls are resolved at compile time.
- It generates a helper function that "knows how" to move and dispose of the object. A pointer to that template function is stored with the object.
- The "dispose" function for small objects explicitly calls the destructor on the object in the small object buffer. The dispose for large objects uses
deleteto be symmetrical with large objectnew.
To use it you make a map or deque to construct in place or move construct into place. You can then use the
R v = it->invoke(A1, A2...) to execute R T::operator()(A1,A2...) on the stored instance.Note that it actually allows a lot of different types of lambdas to be stored in the same container (
deque/map/etc), so it is not directly comparable with std::function.Questions:
- Am I missing anything important? What won't work? Is there any Undefined Behaviour?
```
#include
#include
#include
#include
#include
#include
#include
#include
#include
// totalSize is the size of the entire object, not the payload
// the actual payload size is less than that by sizeof pointer
template
class CallableStorage
{
public:
static constexpr std::size_t smallSize = totalSize -
sizeof(std::size_
Solution
Don't cast forwarding references to rvalue type
Here, we use
We must use
Is it useful to store functions with different signatures?
We've lost a lot of the convenience of
Size calculation is wrong
It looks like we've omitted the
(I shuffled things around in my copy to give
Just a note about terminology in the comment -
Unnecessary headers
We're including way too many Standard Library headers (but still omitting one we do need, for
Here, we use
std::move() on a type that could be an lvalue reference:template
CallableStorage(T&& fn)
: CallableStorage(typename std::integral_constant::type(),
std::move(fn))
{
}We must use
std::forward instead, and/or constrain T to be an rvalue reference type:static_assert(std::is_rvalue_reference::value,
"CallableStorage requires a moveable argument");Is it useful to store functions with different signatures?
We've lost a lot of the convenience of
operator(), as we now have to specify template arguments to invoke(). I think it would be better to have the function signature as part of the type, unless there's a demonstrated need for heterogeneous function storage. At present, it seems like we're pushing too much knowledge onto the user.Size calculation is wrong
It looks like we've omitted the
size member when computing the size for Storage. It should bestatic constexpr std::size_t smallSize =
totalSize - sizeof (helper_type) - sizeof (std::size_t);(I shuffled things around in my copy to give
helper a named type; I think that's worthwhile, to reduce accidents)Just a note about terminology in the comment -
sizeof pointer is ambiguous here, because function pointers are not necessarily the same size as object pointers in C++.Unnecessary headers
We're including way too many Standard Library headers (but still omitting one we do need, for
std::aligned_storage). I believe these are what's required:#include
#include
#include Code Snippets
template<typename T>
CallableStorage(T&& fn)
: CallableStorage(typename std::integral_constant<
bool, sizeof(T) <= smallSize>::type(),
std::move(fn))
{
}static_assert(std::is_rvalue_reference<T&&>::value,
"CallableStorage requires a moveable argument");static constexpr std::size_t smallSize =
totalSize - sizeof (helper_type) - sizeof (std::size_t);#include <cstdint>
#include <type_traits>
#include <utility>Context
StackExchange Code Review Q#127807, answer score: 2
Revisions (0)
No revisions yet.