以下为个人学习笔记整理。参考 PhysX SDK 3.4.0 文档,部分代码可能来源于更高版本。

# Memory Management

PhysX 所有对象的分配都是基于 Foundation 内的 PxAllocatorCallback 完成,这里以 PsPool 作为按理简单介绍一下

先来看看 Pool 的构造函数,这里稍微有些绕:

// 通过泛型模板定义好该类型的分配器 Alloc
template <class T, class Alloc = typename AllocatorTraits<T>::Type>
    class PoolBase : public UserAllocated, public Alloc
    {
        PX_NOCOPY(PoolBase)
            protected:
        PoolBase(const Alloc& alloc, uint32_t elementsPerSlab, uint32_t slabSize)
            : Alloc(alloc), mSlabs(alloc), mElementsPerSlab(elementsPerSlab), mUsed(0), mSlabSize(slabSize), mFreeElement(0)
            {
                PX_COMPILE_TIME_ASSERT(sizeof(T) >= sizeof(size_t));
            }
    }

这里接收一个 T 类型的模板类,并根据模板类声明对应的模板类内存分配器 AllocAllocatorTraits<T>::Type 分为两类,这个后面再详细介绍:

  • 基于名字查找的分配 ——NamedAllocator
  • 不基于名字查找的分配 ——ReflectionAllocator
template <typename T>
struct AllocatorTraits
{
#if PX_USE_NAMED_ALLOCATOR
	typedef NamedAllocator Type;
#else
	typedef ReflectionAllocator<T> Type;
#endif
};

不论哪一个,其提供的 allocate 函数都会最终指向 Foundationallocate 函数:

// NamedAllocator
void* NamedAllocator::allocate(size_t size, const char* filename, int line)
{
	if(!size)
		return 0;
	Foundation::Mutex::ScopedLock lock(getMutex());
	const AllocNameMap::Entry* e = getMap().find(this);
	PX_ASSERT(e);
	return getAllocator().allocate(size, e->second, filename, line);
}
// ReflectionAllocator<T>
void* allocate(size_t size, const char* filename, int line)
{
    return size ? getAllocator().allocate(size, getName(), filename, line) : 0;
}
//getAllocator 实际上是从全局单例 Foundation 获取分配器对象
PxAllocatorCallback& getAllocator()
{
	return getFoundation().getAllocator();
}

细心的小伙伴应该发现了两者的细微差别:NamedAllocator 里使用了名字字典并且为了保证线程安全还加了锁,这个我们后面再讲。

下面先来看看 FoundationgetAllocator 里面获取到的到底是什么:🤔

// Foundation 的分配器获取实现如下:
PxAllocatorCallback& getAllocator()
{
    return mBroadcastingAllocator;
}
// 分配器的注册可以由开发者指定,一般情况下都是使用默认分配器 PxDefaultAllocator
physx::PxFoundation* PxCreateFoundation(physx::PxU32 version, physx::PxAllocatorCallback& allocator,
                                        physx::PxErrorCallback& errorCallback)
{
       return physx::shdfnd::Foundation::createInstance(version, errorCallback, allocator);
}
// Foundation 自身是个单例
Foundation* Foundation::createInstance(PxU32 version, PxErrorCallback& errc, PxAllocatorCallback& alloc)
{
	if(!mInstance)
	{
		mInstance = reinterpret_cast<Foundation*>(alloc.allocate(sizeof(Foundation), "Foundation", __FILE__, __LINE__));
		// ...
        PX_PLACEMENT_NEW(mInstance, Foundation)(errc, alloc);
        // ...
    }
	return 0;
}
    
//Foundation 的构造函数,其中 mAllocatorCallback 和 mBroadcastingAllocator 都是通过 alloc 对象进行内存分配
Foundation::Foundation(PxErrorCallback& errc, PxAllocatorCallback& alloc) : 
mAllocatorCallback(alloc), 	// 在这里被初始化
mBroadcastingAllocator(alloc, errc) // ...
{}

getAllocator 返回的是一个 PxAllocatorCallback 的对象,在初始化 Foundation 的时候被设置。

一般情况下会使用默认的分配器进行初始化,当然也可以开发者指定:

class PxDefaultAllocator : public PxAllocatorCallback
{
public:
	void* allocate(size_t size, const char*, const char*, int)
	{
        //linux 用 malloc, window 用 _aligned_malloc, 保证 16 字节对齐
		void* ptr = platformAlignedAlloc(size);
		PX_ASSERT((reinterpret_cast<size_t>(ptr) & 15)==0);
		return ptr;
	}
	void deallocate(void* ptr)
	{
		platformAlignedFree(ptr);
	}
};

# 内存分配器

前面简单介绍了 Physx 的两种内存分配器,这里在做一个小小的展开🪂

# NamedAllocator

名字查找的分配器会在 CHECK 或者 DEBUG 模式下开启:

#if PX_DEBUG || PX_CHECKED
#define PX_USE_NAMED_ALLOCATOR 1
#else
#define PX_USE_NAMED_ALLOCATOR 0
#endif

由于名字查找的分配模式需要操作 Foundation 内部的一个名字字典,每次查改都需要加锁,所以效率会慢一些:

PX_INLINE AllocNameMap& getMap()
{
	return getFoundation().getNamedAllocMap();
}
// 初始化的时候新增(加锁)
NamedAllocator::NamedAllocator(const char* name)
{
	Foundation::Mutex::ScopedLock lock(getMutex());
	getMap().insert(this, name);
}
// 内存分配的时候查找(加锁)
void* NamedAllocator::allocate(size_t size, const char* filename, int line)
{
	if(!size)
		return 0;
	Foundation::Mutex::ScopedLock lock(getMutex());
	const AllocNameMap::Entry* e = getMap().find(this);
	PX_ASSERT(e);
	return getAllocator().allocate(size, e->second, filename, line);
}

# ReflectionAllocator

初见 Reflection 还以为通过反射机制来实现内存分配,想着蛮吊的。

细看之下越发的疑惑,压根没有任何反射逻辑,单纯是不使用名字查找,且名字通过 C++ 内置的 typeid(T).name() 自动生成。🤕

template <typename T>
class ReflectionAllocator
{
	static const char* getName()
	{	
        // ...
		return typeid(T).name();
	}
  public:
	ReflectionAllocator(const char* = 0)
	{
	}
	
	void* allocate(size_t size, const char* filename, int line)
	{
		return size ? getAllocator().allocate(size, getName(), filename, line) : 0;
	}
	void deallocate(void* ptr)
	{
		if(ptr)
			getAllocator().deallocate(ptr);
	}
};

效率上面比 NamedAllocator 高的原因大概是省略了名字字典的操作,避免额外的加锁。

另外感叹一下这个类做成模板类看起来仅仅只为了获取 typeid(T) ,属实有些大材小用。

todo...

更新于 阅读次数

请我[恰饭]~( ̄▽ ̄)~*

鑫酱(●'◡'●) 微信支付

微信支付

鑫酱(●'◡'●) 支付宝

支付宝