首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >小对象优化的无拷贝缓存线对齐函数存储对象

小对象优化的无拷贝缓存线对齐函数存储对象
EN

Code Review用户
提问于 2016-05-08 03:44:56
回答 1查看 283关注 0票数 6

我实现了一个std::function-like类,

  • 它是为存储lambda而优化的
  • 从来不复制存储的函数,
  • 它有一个小缓冲区来存储小函式,避免了动态内存分配。
  • 因为函子可能会从不同的核心被访问,所以它的大小被设计成精确的高速缓存线的倍数。
  • 存储存储对象的大小,以便能够决定将其视为小对象还是大对象。
  • 允许一个存储容器存储许多不同类型的可调用对象。

另外,

  • 太大的对象自动返回到new,并且只存储一个指针。
  • 支持裸函数指针,并存储在小对象缓冲区中。
  • 它不使用vtbl进行operator()代码绘制。调用在编译时解析。
  • 它生成一个助手函数,它“知道”如何移动和处置对象。指向该模板函数的指针与对象一起存储。
  • 小对象的"dispose“函数显式地调用小对象缓冲区中对象上的析构函数。对大型对象的处理使用delete与大对象new对称。

要使用它,您可以制作一张地图或设备,以便在适当的位置构建或移动构造。然后可以使用R v = it->invoke<R, T, A1, A2...>(A1, A2...)对存储的实例执行R T::operator()(A1,A2...)

请注意,它实际上允许将许多不同类型的lambda存储在同一个容器(deque/map/etc)中,因此不能与std::function直接比较。

问题:

  • 我错过了什么重要的事吗?什么不管用?有什么不明确的行为吗?
代码语言:javascript
复制
#include <unordered_map>
#include <vector>
#include <memory>
#include <cstdint>
#include <set>
#include <deque>
#include <algorithm>
#include <functional>
#include <utility>

// totalSize is the size of the entire object, not the payload
// the actual payload size is less than that by sizeof pointer
template<std::size_t totalSize = 128>
class CallableStorage
{
public:
    static constexpr std::size_t smallSize = totalSize -
            sizeof(std::size_t(*)(void*));

    using Storage =
            typename std::aligned_storage<smallSize>::type;

    template<typename T>
    CallableStorage(T&& fn)
        : CallableStorage(typename std::integral_constant<
                         bool, sizeof(T) <= smallSize>::type(),
                         std::move(fn))
    {
    }

    CallableStorage(CallableStorage const&) = delete;
    CallableStorage& operator=(CallableStorage const&) = delete;

    CallableStorage(CallableStorage&& r)
        : size(r.size)
        , helper(r.helper)
    {
        MovePair places;
        if (size <= smallSize)
        {
            places.from = &r;
            places.to = this;
            r.helper(HelperCommand::Move, &places);
        }
        else
        {
            payload.largeStorage = r.payload.largeStorage;
            r.payload.largeStorage = nullptr;
        }
    }

    CallableStorage& operator=(CallableStorage&&) = delete;

    ~CallableStorage()
    {
        if (size <= smallSize)
            helper(HelperCommand::Dispose, &payload.smallStorage);
        else
            helper(HelperCommand::Dispose, payload.largeStorage);
    }

    template<typename T>
    T* lambdaPointer()
    {
        return lambdaPointer<T>(
                    typename std::integral_constant<
                    bool, sizeof(T) <= smallSize>::type());
    }

    template<typename T>
    T const* lambdaPointer() const
    {
        return lambdaPointer<T>(
                    typename std::integral_constant<
                    bool, sizeof(T) <= smallSize>::type());
    }

    template<typename R, typename T, typename... Args>
    R invoke(Args&& ...args)
    {
        return invoke<R, T, Args...>(
                    typename std::integral_constant<
                    bool, sizeof(T) <= smallSize>::type(),
                    std::forward<Args>(args)...);
    }

private:
    enum class HelperCommand
    {
        Dispose,    // pointer to object
        Move        // pointer to MovePair
    };

    struct MovePair
    {
        void* to;
        void* from;
    };

    template<typename R, typename T, typename... Args>
    R invoke(std::true_type, Args&& ...args)
    {
        auto ptr = reinterpret_cast<T*>(&payload.smallStorage);
        return ptr->operator()(std::forward<Args>(args)...);
    }

    template<typename R, typename T, typename... Args>
    R invoke(std::false_type, Args&& ...args)
    {
        auto ptr = reinterpret_cast<T*>(payload.largeStorage);
        return ptr->operator()(std::forward<Args>(args)...);
    }

    template<typename T>
    CallableStorage(std::true_type, T&& fn)
        : size(sizeof(T))
        , helper(lambdaHelper<T>)
    {
        new (&payload.smallStorage) T(std::move(fn));
    }

    template<typename T>
    CallableStorage(std::false_type, T&& fn)
        : size(sizeof(T))
        , helper(lambdaHelper<T>)
    {
        payload.largeStorage = new T(std::move(fn));
    }

    template<typename T>
    T* lambdaPointer(std::true_type)
    {
        return reinterpret_cast<T*>(&payload.smallStorage);
    }

    template<typename T>
    T const* lambdaPointer(std::true_type) const
    {
        return reinterpret_cast<T const*>(&payload.smallStorage);
    }

    template<typename T>
    T* lambdaPointer(std::false_type)
    {
        return reinterpret_cast<T*>(payload.largeStorage);
    }

    template<typename T>
    T const* lambdaPointer(std::false_type) const
    {
        return reinterpret_cast<T const*>(payload.largeStorage);
    }

    template<typename T>
    static std::size_t lambdaHelper(HelperCommand cmd, void* p)
    {
        if (p)
        {
            using IsSmall = typename std::integral_constant<bool,
                sizeof(T) <= smallSize>::type;

            switch (cmd)
            {
            case HelperCommand::Dispose:
                lambdaDisposerImpl<T>(IsSmall(), p);
                break;

            case HelperCommand::Move:
                auto const& move = *reinterpret_cast<MovePair const*>(p);
                lambdaMoveImpl<T>(IsSmall(), move);
                break;
            }

        }

        return sizeof(T);
    }

    template<typename T>
    static void lambdaDisposerImpl(std::true_type, void* p)
    {
        reinterpret_cast<T*>(p)->~T();
    }

    template<typename T>
    static void lambdaDisposerImpl(std::false_type, void* p)
    {
        delete reinterpret_cast<T*>(p);
    }

    template<typename T>
    static void lambdaMoveImpl(std::true_type, MovePair const& p)
    {
        CallableStorage& lhs = *reinterpret_cast<CallableStorage*>(p.to);
        CallableStorage& rhs = *reinterpret_cast<CallableStorage*>(p.from);

        T* from = reinterpret_cast<T*>(&rhs.payload.smallStorage);
        T* to = reinterpret_cast<T*>(&lhs.payload.smallStorage);

        new (to) T(std::move(*from));
    }

    template<typename T>
    static void lambdaMoveImpl(std::false_type, MovePair const& )
    {
    }

    union Payload
    {
        Storage smallStorage;
        void* largeStorage;
    };

    Payload payload;
    std::size_t size;
    std::size_t (*helper)(HelperCommand, void*);
};
EN

回答 1

Code Review用户

发布于 2018-06-05 09:16:33

不转换对rvalue类型

的转发引用

在这里,我们对可能是lvalue引用的类型使用std::move()

代码语言:javascript
复制
template<typename T>
CallableStorage(T&& fn)
    : CallableStorage(typename std::integral_constant<
                      bool, sizeof(T) <= smallSize>::type(),
                      std::move(fn))
{
}

我们必须使用std::forward<T>,并且/或约束T为rvalue引用类型:

代码语言:javascript
复制
    static_assert(std::is_rvalue_reference<T&&>::value,
                  "CallableStorage requires a moveable argument");

用不同的签名存储函数有用吗?

我们已经失去了operator()的许多便利,因为我们现在必须为invoke()指定模板参数。我认为最好将函数签名作为类型的一部分,除非对异构函数存储有明确的需求。目前,我们似乎把太多的知识强加给了用户。

大小计算错误

在计算size的大小时,我们似乎忽略了Storage成员。它应该是

代码语言:javascript
复制
static constexpr std::size_t smallSize =
    totalSize - sizeof (helper_type) - sizeof (std::size_t);

(为了给helper命名类型,我在我的副本中到处乱动,我认为这是值得的,以减少事故)

这里只需说明一下注释- sizeof pointer中的术语,因为函数指针不一定与C++中的对象指针大小相同。

不必要的报头

我们包含了太多的标准库头(但对于std::aligned_storage,我们仍然忽略了我们确实需要的)。我相信这些都是必需的:

代码语言:javascript
复制
#include <cstdint>
#include <type_traits>
#include <utility>
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/127807

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档