【问题标题】:memory heap allocator library that keeps separate structures?保持单独结构的内存堆分配器库?
【发布时间】:2015-07-31 17:06:42
【问题描述】:

这是我的问题:我需要管理我的程序无法读取或写入的远程连续缓冲区中的内存。它需要具有 malloc()/free() 语义,并支持设置最小对齐和避免碎片(尽可能)。由于我无法直接读取或写入此缓冲区,因此我需要使用本地结构来管理所有分配。

我已经在使用 boost,所以如果可以按摩 boost 内部的某些东西来做到这一点,那就太好了。但是,我并不反对使用 C 库或类似的东西。

例如,我需要一个非 IPC 版本:

boost::interprocess::basic_managed_external_buffer<
                     char,
                     boost::interprocess::rbtree_best_fit<
                                          boost::interprocess::mutex_family,
                                          boost::interprocess::offset_ptr<void>,
                                          SOME_ALIGNMENT>,
                     boost::interprocess::iset_index>

最好使用 malloc/free 语义而不是 new/delete 但没有它实际读取或写入底层缓冲区(并将所有分配信息/数据结构保存在单独的缓冲区中)

有什么想法吗?

附:我不希望 boost::interprocess 示例具有误导性,我只是熟悉该界面,因此将其用作示例。该应用程序不是真正的进程间,分配器只会在我的应用程序中使用。

具体来说,我希望能够管理一个 16GB 的外部缓冲区,其分配大小从 128 字节一直到 512MB。这是严格的 64 位代码,但即便如此,我还是希望指针类型成为模板参数,这样我就可以显式使用 uint64_t。

【问题讨论】:

标签: c++ memory memory-management boost


【解决方案1】:

我将发布关于我们实际完成的工作的最新信息。我最终实现了自己的远程内存分配器(来源如下)。它在精神上类似于answer Sam suggests,但使用 boost 侵入式 RB 树来避免在释放、加入等时进行一些 log(N) 查找。它是线程安全的,并支持各种远程指针/偏移类型作为模板参数。它在很多方面可能并不理想,但它对于我们需要它做的事情来说已经足够好了。如果您发现错误,请告诉我。

/*
 * Thread-safe remote memory allocator
 *
 * Author: Yuriy Romanenko
 * Copyright (c) 2015 Lytro, Inc.
 *
 */

#pragma once

#include <memory>
#include <mutex>
#include <cstdint>
#include <cstdio>
#include <functional>

#include <boost/intrusive/rbtree.hpp>

namespace bi = boost::intrusive;

template<typename remote_ptr_t = void*,
         typename remote_size_t = size_t,
         typename remote_uintptr_t = uintptr_t>
class RemoteAllocator
{
    /* Internal structure used for keeping track of a contiguous block of
     * remote memory. It can be on one or two of the following RB trees:
     *    Free Chunks (sorted by size)
     *    All Chunks (sorted by remote pointer)
     */
    struct Chunk
    {
        bi::set_member_hook<> mRbFreeChunksHook;
        bi::set_member_hook<> mRbAllChunksHook;

        remote_uintptr_t mOffset;
        remote_size_t mSize;
        bool mFree;

        Chunk(remote_uintptr_t off, remote_size_t sz, bool fr)
                : mOffset(off), mSize(sz), mFree(fr)
        {

        }

        bool contains(remote_uintptr_t off)
        {
            return (off >= mOffset) && (off < mOffset + mSize);
        }
    private:
        Chunk(const Chunk&);
        Chunk& operator=(const Chunk&);
    };

    struct ChunkCompareSize : public std::binary_function <Chunk,Chunk,bool>
    {
        bool operator() (const Chunk& x, const Chunk& y) const
        {
            return x.mSize < y.mSize;
        }
    };
    struct ChunkCompareOffset : public std::binary_function <Chunk,Chunk,bool>
    {
        bool operator() (const Chunk& x, const Chunk& y) const
        {
            return x.mOffset < y.mOffset;
        }
    };

    typedef bi::rbtree<Chunk,
                       bi::member_hook<Chunk,
                                       bi::set_member_hook<>,
                                       &Chunk::mRbFreeChunksHook>,
                       bi::compare< ChunkCompareSize > > FreeChunkTree;

    typedef bi::rbtree<Chunk,
                       bi::member_hook<Chunk,
                                       bi::set_member_hook<>,
                                       &Chunk::mRbAllChunksHook>,
                       bi::compare< ChunkCompareOffset > > AllChunkTree;

    // Thread safety lock
    std::mutex mLock;
    // Size of the entire pool
    remote_size_t mSize;
    // Start address of the pool
    remote_ptr_t mStartAddr;

    // Tree of free chunks
    FreeChunkTree mFreeChunks;
    // Tree of all chunks
    AllChunkTree mAllChunks;

    // This removes the chunk from both trees
    Chunk *unlinkChunk(Chunk *c)
    {
        mAllChunks.erase(mAllChunks.iterator_to(*c));
        if(c->mFree)
        {
            mFreeChunks.erase(mFreeChunks.iterator_to(*c));
        }
        return c;
    }

    // This reinserts the chunk into one or two trees, depending on mFree
    Chunk *relinkChunk(Chunk *c)
    {
        mAllChunks.insert_equal(*c);
        if(c->mFree)
        {
            mFreeChunks.insert_equal(*c);
        }
        return c;
    }

    /* This assumes c is 'free' and walks the mAllChunks tree to the left
     * joining any contiguous free chunks into this one
     */
    bool growFreeLeft(Chunk *c)
    {
        auto it = mAllChunks.iterator_to(*c);
        if(it != mAllChunks.begin())
        {
            it--;
            if(it->mFree)
            {
                Chunk *left = unlinkChunk(&(*it));
                unlinkChunk(c);
                c->mOffset = left->mOffset;
                c->mSize = left->mSize + c->mSize;
                delete left;
                relinkChunk(c);
                return true;
            }
        }
        return false;
    }
    /* This assumes c is 'free' and walks the mAllChunks tree to the right
     * joining any contiguous free chunks into this one
     */
    bool growFreeRight(Chunk *c)
    {
        auto it = mAllChunks.iterator_to(*c);
        it++;
        if(it != mAllChunks.end())
        {
            if(it->mFree)
            {
                Chunk *right = unlinkChunk(&(*it));
                unlinkChunk(c);
                c->mSize = right->mSize + c->mSize;
                delete right;
                relinkChunk(c);
                return true;
            }
        }
        return false;
    }

public:
    RemoteAllocator(remote_size_t size, remote_ptr_t startAddr) :
        mSize(size), mStartAddr(startAddr)
    {
        /* Initially we create one free chunk the size of the entire managed
         * memory pool, and add it to both trees
         */
        Chunk *all = new Chunk(reinterpret_cast<remote_uintptr_t>(mStartAddr),
                               mSize,
                               true);
        mAllChunks.insert_equal(*all);
        mFreeChunks.insert_equal(*all);
    }

    ~RemoteAllocator()
    {
        auto it = mAllChunks.begin();

        while(it != mAllChunks.end())
        {
            Chunk *pt = unlinkChunk(&(*it++));
            delete pt;
        }
    }

    remote_ptr_t malloc(remote_size_t bytes)
    {
        std::unique_lock<std::mutex> lock(mLock);
        auto fit = mFreeChunks.lower_bound(
                    Chunk(reinterpret_cast<remote_uintptr_t>(mStartAddr),
                          bytes,
                          true));

        /* Out of memory */
        if(fit == mFreeChunks.end())
            return remote_ptr_t{0};

        Chunk *ret = &(*fit);
        /* We need to split the chunk because it's not the exact size */
        /* Let's remove the node */
        mFreeChunks.erase(fit);

        if(ret->mSize != bytes)
        {
            Chunk *right, *left = ret;

            /* The following logic decides which way the heap grows
             * based on allocation size. I am not 100% sure this actually
             * helps with fragmentation with such a big threshold (50%)
             *
             * Check if we will occupy more than half of the chunk,
             * in that case, use the left side. */
            if(bytes > ret->mSize / 2)
            {
                right = new Chunk(left->mOffset + bytes,
                                  left->mSize - bytes,
                                  true);
                relinkChunk(right);

                left->mSize = bytes;
                left->mFree = false;

                ret = left;
            }
            /* We'll be using less than half, let's use the right side. */
            else
            {
                right = new Chunk(left->mOffset + left->mSize - bytes,
                                  bytes,
                                  false);

                relinkChunk(right);

                left->mSize = left->mSize - bytes;
                mFreeChunks.insert_equal(*left);

                ret = right;
            }
        }
        else
        {
            ret->mFree = false;
        }

        return reinterpret_cast<remote_ptr_t>(ret->mOffset);
    }

    remote_ptr_t malloc_aligned(remote_size_t bytes, remote_size_t alignment)
    {
        remote_size_t bufSize = bytes + alignment;
        remote_ptr_t mem = this->malloc(bufSize);
        remote_ptr_t ret = mem;
        if(mem)
        {
            remote_uintptr_t offset = reinterpret_cast<remote_uintptr_t>(mem);
            if(offset % alignment)
            {
                offset = offset + (alignment - (offset % alignment));
            }
            ret = reinterpret_cast<remote_ptr_t>(offset);
        }
        return ret;
    }

    void free(remote_ptr_t ptr)
    {
        std::unique_lock<std::mutex> lock(mLock);
        Chunk ref(reinterpret_cast<remote_uintptr_t>(ptr), 0, false);
        auto it = mAllChunks.find(ref);
        if(it == mAllChunks.end())
        {
            it = mAllChunks.upper_bound(ref);
            it--;
        }
        if(!(it->contains(ref.mOffset)) || it->mFree)
            throw std::runtime_error("Could not find chunk to free");

        Chunk *chnk = &(*it);
        chnk->mFree = true;
        mFreeChunks.insert_equal(*chnk);

        /* Maximize space */
        while(growFreeLeft(chnk));
        while(growFreeRight(chnk));
    }

    void debugDump()
    {
        std::unique_lock<std::mutex> lock(mLock);
        int i = 0;
        printf("----------- All chunks -----------\n");
        for(auto it = mAllChunks.begin(); it != mAllChunks.end(); it++)
        {
            printf(" [%d] %lu -> %lu (%lu) %s\n",
                i++,
                it->mOffset,
                it->mOffset + it->mSize,
                it->mSize,
                it->mFree ? "(FREE)" : "(NOT FREE)");
        }
        i = 0;
        printf("----------- Free chunks -----------\n");
        for(auto it = mFreeChunks.begin(); it != mFreeChunks.end(); it++)
        {
            printf(" [%d] %lu -> %lu (%lu) %s\n",
                i++,
                it->mOffset,
                it->mOffset + it->mSize,
                it->mSize,
                it->mFree ? "(FREE)" : "(NOT FREE)");
        }
    }
};

【讨论】:

    【解决方案2】:

    我不知道有什么可以使用的固定实现。但是,您自己实现这似乎并不特别困难,只需使用 C++ 标准库中的各种容器即可。

    我会推荐一种使用两个std::maps 和一个std::multimap 的简单方法。假设bufaddr_t 是一个不透明的整数,表示外部缓冲区中的地址。由于我们谈论的是 16 gig 缓冲区,因此它必须是 64 位地址:

    typedef uint64_t memblockaddr_t;
    

    分配块的大小同上。

    typedef uint64_t memblocksize_t;
    

    我想,你可以为 memblockaddr_t 使用其他东西,只要不透明数据类型具有严格的弱排序。

    第一部分很简单。跟踪所有分配的块:

    std::map<memblockaddr_t, memblocksize_t> allocated;
    

    因此,当您在外部缓冲区中成功分配一块内存时,您将其插入此处。当您希望释放一块内存时,您可以在此处查找已分配块的大小,然后删除映射条目。很简单。

    但这当然不是全部。现在,我们需要跟踪可用的、未分配的内存块。让我们这样做:

    typedef std::multimap<memblocksize_t, memblockaddr_t> unallocated_t;
    
    unallocated_t unallocated;
    
    std::map<memblockaddr_t, unallocated_t::iterator> unallocated_lookup;
    

    unallocated 是外部缓冲区中所有未分配块的集合,以块大小为键。关键是块大小。因此,当您需要分配一块特定大小的内存时,您可以简单地使用lower_bound() 方法(或upper_bound(),如果您愿意)立即找到第一个大小与您一样大的内存块想分配。

    当然,既然你可以有许多相同大小的块,unallocated 必须是std::multimap

    另外,unallocated_lookup 是一个以每个未分配块的地址为键的映射,它为您提供了该块在unallocated 中的条目的迭代器。为什么你需要它,一会儿就会清楚。

    所以:

    使用单个条目初始化一个新的、完全未分配的缓冲区:

    memblockaddr_t beginning=0; // Or, whatever represents the start of the buffer.
    auto p=unallocated.insert(std::make_pair(BUFFER_SIZE, beginning)).first;
    unallocated_lookup.insert(std::make_pair(beginning, p));
    

    然后:

      1234563如果超出了您的需要,请将多余的部分返回到池中,就好像您不需要的额外数量正在被释放(下面的第 3 步)。最后,将其插入到allocated 数组中,这样您就可以记住分配的块有多大。
    1. 要释放一个块,在allocated 数组中查找它,获取它的大小,从allocated 数组中删除它,然后:

    2. 将其插入unallocatedunallocated_lookup,类似于插入初始未分配块的方式,见上文。

    3. 但你还没有完成。然后,您必须使用unallocated_lookup 在内存缓冲区中查找前面的未分配块和后面的未分配块。如果它们中的任何一个或两个都紧邻新释放的块,则必须将它们合并在一起。这应该是一个非常明显的过程。您可以简单地从unallocatedunallocated_lookup 中分别正式删除相邻的块,然后释放一个合并的块。

    这就是unallocated_lookup 的真正目的,能够轻松合并连续的未分配块。

    据我所知,上述所有操作都具有对数复杂度。它们完全基于 std::mapstd::multimap 的具有对数复杂度的方法,仅此而已。

    最后:

    根据您的应用程序的行为,您可以轻松地调整实现以在内部将分配的块的大小四舍五入到您希望的任何倍数。或者调整分配策略——从足够大以满足分配请求的最小块开始分配,或者只从未分配的大块开始分配(简单,使用end() 找到它)等等...

    这是滚动您自己的实现的一个优势 - 您将始终拥有更大的灵活性来调整自己的实现,然后您通常会使用一些罐装的外部库。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 2022-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-01
      • 2016-01-27
      相关资源
      最近更新 更多