服务器3D场景建模(八):四叉树的内存优化

优化原因

四叉树节点的分裂与合并比较频繁,如果直接 new、delete,相对比较慢。

因此有比较做下内存优化

内存分配器基类

自己写的内存分配器,不一定够完美、或者使用者有自己的内存分配策略。

需要定义下基类,方便别人可以组装自己的内存分配器。

代码如下:

template<typename T>
class MemBase
{
public:
    MemBase() {}
    virtual ~MemBase() {}

    virtual void* _alloc(size_t size) = 0;
    virtual void _free(void* ptr) = 0;

    template<typename ... Args>
    inline T* New(Args... args)
    {
        void *ptr = _alloc(sizeof(T));
        return new (ptr)T(args...);
    }
    inline void Delete(T* ptr)
    {
        ptr->~T();
        _free(ptr);
    }
};

继承 MemBase,并实现 _alloc、_free函数的,都可以组装到四叉树上。

四叉树定义如下:

template<typename TItem, unsigned NodeCapacity, unsigned LevelLimit, typename TAlloc>
class QuadTree
{
    // ...(略)...
}

TAlloc 提供了组装自定义内存分配器接口

基于块的内存分配策略

简单的new方式一些对象,并组成一个对象池的话,内存碎片会比较严重。

可以需要时,每次预先分配一大块内存。再在大块内存上,切出一个个对象地址。

这样可以比较有效的减缓内存碎片现象。

代码如下:

template<typename T, unsigned BlockSize = 4096>
class Blocks : public MemBase<T>
{
    public:
    using TMallocFunc = std::function<void*(size_t)>;
    using TFreeFunc = std::function<void(void*)>;

    Blocks(const TMallocFunc& f1, const TFreeFunc& f2)
        : mBlocks(nullptr)
        , mHead(nullptr)
        , mMalloc(f1)
        , mFree(f2)
    {
    }

    ~Blocks()
    {
        while (mBlocks)
        {
            Item* next = mBlocks->next;
            mFree(mBlocks);
            mBlocks = next;
        }
    }

    inline void* _alloc(size_t size)
    {
        if (!mHead)
        {
            newBlock();
        }
        void* ptr = mHead;
        mHead = mHead->next;
        return ptr;
    }

    inline void _free(void* ptr)
    {
        Item* p = (Item*)ptr;
        p->next = mHead;
        mHead = p;
    }

private:
    void newBlock()
    {
        assert(!mHead);
        Item* ptr = (Item*)mMalloc(BlockSize);
        if (mBlocks)
        {
            ptr->next = mBlocks;
            mBlocks = ptr;
        }
        else
        {
            mBlocks = ptr;
            mBlocks->next = 0;
        }
        ptr = ptr + 1;

#define PTR(N) ((Item*)((char*)ptr + (N) * sizeof(T)))
        mHead = PTR(0);
        size_t lstIndex = (BlockSize - sizeof(Item)) / sizeof(T) - 1;
        for (size_t i = 0; i < lstIndex; i++)
        {
            PTR(i)->next = PTR(i + 1);
        }
        PTR(lstIndex)->next = nullptr;
#undef PTR
    }

    struct Item
    {
        Item* next;
    };
    Item* mBlocks;
    Item* mHead;
    TMallocFunc mMalloc;
    TFreeFunc mFree;
};

对象池不够用时,继续分配块;有块组成对象池。在块上做链表,这样可以方便的得到下个对象地址。

内存对齐

内存对齐,让CPU可以1次获取字段数据。

代码如下:

template<typename T, unsigned BlockSize = 4096>
class Mem : public Blocks<T, BlockSize>
{
public:
    Mem() : Blocks<T, BlockSize>(malloc, free)
    {
    }

    ~Mem()
    {
    }
};

template<typename T, unsigned BlockSize = 4096, unsigned Alignment = 4>
class AlignedMem : public Blocks<T, BlockSize>
{
public:
#ifdef _MSC_VER
    AlignedMem() : Blocks<T, BlockSize>(std::bind(_aligned_malloc, std::placeholders::_1, Alignment), _aligned_free)
#else
    AlignedMem() : Blocks<T, BlockSize>(std::bind(aligned_alloc, Alignment, std::placeholders::_1), free)
#endif
    {
    }

    ~AlignedMem()
    {
    }
};

性能对比

图1

如图,new/delete的方式很慢。

内存对齐的方式最快。

据说,内存对齐的优势是在访问对象字段时,因此本测试还未完全测试出其效果。

测试代码:
https://github.com/fananchong/aoi/blob/master/tests/test1/main.cpp

本文内存分配器地址:
https://github.com/fananchong/aoi/blob/master/impl/alloc.h

结束语

内存分配相关,本人也了解不多。

比如本代码有优化改进之处,望留言指正。

猜你喜欢

转载自blog.csdn.net/u013272009/article/details/80271980
今日推荐