以下为个人学习笔记整理。参考 PhysX SDK 3.4.0 文档,部分代码可能来源于更高版本。
# PhysX——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 类型的模板类,并根据模板类声明对应的模板类内存分配器 Alloc。 AllocatorTraits<T>::Type 分为两类,这个后面再详细介绍:
- 基于名字查找的分配 ——NamedAllocator
 - 不基于名字查找的分配 ——ReflectionAllocator。
 
template <typename T>  | |
struct AllocatorTraits  | |
{ | |
#if PX_USE_NAMED_ALLOCATOR | |
typedef NamedAllocator Type;  | |
#else | |
typedef ReflectionAllocator<T> Type;  | |
#endif | |
};  | 
不论哪一个,其提供的 allocate 函数都会最终指向 Foundation 的 allocate 函数:
// 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 里使用了名字字典并且为了保证线程安全还加了锁,这个我们后面再讲。
下面先来看看 Foundation 的 getAllocator 里面获取到的到底是什么:🤔
// 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...