【问题标题】:Is there some way to use boost::obect_pool with faster free operations有没有办法使用 boost::object_pool 更快的免费操作
【发布时间】:2016-08-13 18:52:42
【问题描述】:

我使用 boost object_pool 已经有一段时间了,总体上对结果很满意。以前我主要是分配单个对象,但很少单独释放它们,只是一次释放整个池。最近,当我遇到需要从池中释放许多对象时,我发现它非常慢。

显然 pool 正在搜索已发布的块列表以链接新发布的对象。该文档讨论了有序池和无序池,并提到了 pool_allocator 和 fast_pool_allocator。无序池(使用 fast_memory_allocator)大概会更快地释放内存块。但是,我看不到如何使用它。

我是否理解正确,我可以在 pool_allocator 和 fast_pool_allocator 之间进行选择,只能与 boost::singleton_pool 结合使用,而不能与 boost::object_pool 结合使用?

下面是一个说明问题的小测试程序。它是用 VS2013 和 boost 1_57_0 构建的。测试在对象池中分配n个对象,然后随机释放10%。它有一些粗略的计时工具,表明对于 n == 100,000,分配需要 0.004 秒,而释放需要 0.4 秒。同时对于 n == 1,000,000,在我的机器上分配需要 0.022 秒,释放需要 42 秒。

#include <boost/pool/object_pool.hpp>
#include "time.h"
#include <vector>
#include <random>

struct foo {
    int data[10];
};

struct test {
    test(unsigned n) : size{ n } {}
    void run();
    float elapsedSec(clock_t& watch);
    unsigned size;
    boost::object_pool<foo> _pool;
    float mallocSec;
    float freeSec;
};

void test::run() {
    std::vector<foo *> foos(size, nullptr);
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(0, size - 1);
    auto dice = std::bind(distribution, generator);
    clock_t watch = clock();
    for (int i = 0; i < size; ++i)
        foos[i] = _pool.malloc();
    mallocSec = elapsedSec(watch);
    for (int i = 0; i < size / 10;) {
        auto idx = dice();
        if (foos[idx] == nullptr)
            continue;
        _pool.free(foos[idx]);
        foos[idx] = nullptr;
        i += 1;
    }
    freeSec = elapsedSec(watch);
}

float test::elapsedSec(clock_t& watch) {    
    clock_t start = watch;
    watch = clock();
    return (watch - start) / static_cast<float>(CLOCKS_PER_SEC);   
}

【问题讨论】:

  • 告诉我们你被困在哪里。除了您要更改的内容之外,我们需要编译的代码。因为我们肯定不会阅读所有文档,所以创建一个示例应用程序,对其进行修复,以便我们理解您想要的,只是发现您的问题实际上略有不同。 stackoverflow.com/help/how-to-ask
  • 我反对版主从我的声誉中扣分。我没有被卡住,我遇到了提升对象池的已知限制,并询问专家是否知道解决方法。如果有人用谷歌搜索,他会找到多个链接来确认我的断言(包括一些关于堆栈溢出的断言)。我不想通过引用他们来分散读者的注意力。任何从 boost::object_pool 分配数百万个对象然后尝试释放数十万个对象的小程序都会表现出严重的性能问题。我希望这是该领域的专家都知道的事实。
  • 1.我们不是版主,只是试图回答的同行:) 2. 我是那个要求代码的人。 3. 我没有“拿分”(downvote)。如果问题无法改进,我可能会:)
  • 我认为对于熟悉提升池的人来说这个问题很清楚。我编辑了原始帖子以加粗。不要威胁,请解释不清楚的地方。
  • 干得好把试图帮助的人赶走(据我所知也有资格提供帮助)。

标签: c++ memory-management boost


【解决方案1】:

免费调用在底层分配器上命中ordered_free。确实,ordered_malloc_need_resize()takes basically all the execution time

我是否理解正确,我可以在 pool_allocator 和 fast_pool_allocator 之间进行选择,仅与 boost::singleton_pool 结合使用,而不与 boost::object_pool 结合使用?

是的。 object_pool 显然在底层simple_segregated_storage 上使用了ordered_free 函数。这显然是设计使然(尽管我暂时无法理解其基本原理。显然,在预期用途中,object_pool 始终针对数组分配/取消分配进行优化是有意义的)。

我现在理解的方式 pool_allocator 和 fast_pool_allocations 是独立的类,与 singleton_pool 的参数/选项无关。

是的。它们被硬连线以使用单例池实例。 Boost Pool 显然早于标准库对有状态分配器的支持。您可以将fast_pool_allocator 的实现复制出来以使用池的运行时实例而不是单例池。

以下示例使non_boost::fast_pool_allocator 成为特定“对象使用”池实例之上的有状态分配器。这使得分配器是有状态的。状态主要是指向池的指针。

_alloc.destroy 用于破坏foo 实例并释放内存。销毁_pool 后,任何未释放的元素仍将被释放(注意,因为我们不使用object_pool,在这种情况下不会运行foo 的析构函数。在您的示例中, foo 是 POS,因此很容易被破坏。如果不是,你当然可以使用 std::unique_ptr 或类似的,或者确实编写不坚持有序分配的 object_pool 版本。

演示

Live On Coliru

#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
#include <boost/pool/pool_alloc.hpp>
#include "time.h"
#include <vector>
#include <random>

struct foo {
    int data[10];
};

namespace non_boost {
    template <typename T, typename UserAllocator = boost::default_user_allocator_new_delete>
    class fast_pool_allocator
    {
      public:
        typedef T value_type;
        typedef UserAllocator user_allocator;

        typedef value_type * pointer;
        typedef const value_type * const_pointer;
        typedef value_type & reference;
        typedef const value_type & const_reference;
        typedef boost::pool<UserAllocator> pool_type;
        typedef typename pool_type::size_type       size_type;
        typedef typename pool_type::difference_type difference_type;

        template <typename U>
        struct rebind {
            typedef fast_pool_allocator<U, UserAllocator> other;
        };

        pool_type* _ref;
      public:
        fast_pool_allocator(pool_type& ref) : _ref(&ref) { }

        fast_pool_allocator(fast_pool_allocator const&) = default;
        fast_pool_allocator& operator=(fast_pool_allocator const&) = default;

        // Not explicit, mimicking std::allocator [20.4.1]
        template <typename U>
        fast_pool_allocator(const fast_pool_allocator<U, UserAllocator> & other) : _ref(other._ref)
        { }

        // Default destructor used.
        static pointer address(reference r)                     { return &r;                                      } 
        static const_pointer address(const_reference s)         { return &s;                                      } 
        static size_type max_size()                             { return (std::numeric_limits<size_type>::max)(); } 
        void construct(const pointer ptr, const value_type & t) { new (ptr) T(t);                                 } 
        void destroy(const pointer ptr)                         { ptr->~T();                                      } 

        bool operator==(fast_pool_allocator const& rhs) const { return _ref == rhs._ref; }
        bool operator!=(fast_pool_allocator const& rhs) const { return _ref != rhs._ref; }

        pointer allocate(const size_type n)
        {
            const pointer ret = (n == 1) 
                ? static_cast<pointer>( (_ref->malloc)() ) 
                : static_cast<pointer>( _ref->ordered_malloc(n) );
            if (ret == 0)
                boost::throw_exception(std::bad_alloc());
            return ret;
        }

        pointer allocate(const size_type n, const void * const) { return allocate(n); }
        pointer allocate()
        {
            const pointer ret = static_cast<pointer>( (_ref->malloc)() );
            if (ret == 0)
                boost::throw_exception(std::bad_alloc());
            return ret;
        }
        void deallocate(const pointer ptr, const size_type n)
        {

#ifdef BOOST_NO_PROPER_STL_DEALLOCATE
            if (ptr == 0 || n == 0)
                return;
#endif
            if (n == 1)
                (_ref->free)(ptr);
            else
                (_ref->free)(ptr, n);
        }
        void deallocate(const pointer ptr) { (_ref->free)(ptr); }
    };

    //Specialization of fast_pool_allocator<void> required to make the allocator standard-conforming.
    template<typename UserAllocator>
    class fast_pool_allocator<void, UserAllocator> {
    public:
        typedef void*       pointer;
        typedef const void* const_pointer;
        typedef void        value_type;

        template <class U> struct rebind {
            typedef fast_pool_allocator<U, UserAllocator> other;
        };
    };

}

struct test {
    test(unsigned n) : size{ n } {}
    void run();
    float elapsedSec(clock_t& watch);
    unsigned size;

    boost::pool<boost::default_user_allocator_malloc_free> _pool { sizeof(foo) };
    non_boost::fast_pool_allocator<foo, boost::default_user_allocator_malloc_free> _alloc { _pool };

    float mallocSec;
    float freeSec;
};

void test::run() {
    std::vector<foo *> foos(size, nullptr);
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(0, size - 1);

    auto dice = std::bind(distribution, generator);
    clock_t watch = clock();

    for (unsigned i = 0; i < size; ++i)
         foos[i] = _alloc.allocate();

    mallocSec = elapsedSec(watch);

    for (unsigned i = 0; i < size / 10;) {
        auto idx = dice();
        if (foos[idx] != nullptr)
        {
            _alloc.destroy(foos[idx]);
            foos[idx] = nullptr;
        }
        i += 1;
    }

    freeSec = elapsedSec(watch);
}

float test::elapsedSec(clock_t& watch) {    
    clock_t start = watch;
    watch = clock();
    return (watch - start) / static_cast<float>(CLOCKS_PER_SEC);   
}

int main() {
    test t(10u << 20);
    t.run();

    std::cout << t.mallocSec << "\n";
    std::cout << t.freeSec   << "\n";
}

打印(在我的系统上):

0.135127
0.050991

【讨论】:

  • 感谢您的努力和详细的食谱。我真的没想到有人会遇到实施替代池的麻烦。答案远远超出了我预期的简短口头回应。非常感谢。
  • 哦看:我好像健忘了,我已经discovered most of this in March 2015...
猜你喜欢
  • 2023-03-18
  • 1970-01-01
  • 2021-03-28
  • 1970-01-01
  • 1970-01-01
  • 2017-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多