HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

No-copy cache line aligned function storage object with small object optimization

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
linewithfunctionstoragealignedoptimizationcachesmallobjectcopy

Problem

I have implemented a 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 delete to be symmetrical with large object new.



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 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 be

static 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.