|
|
这是一项艰巨的任务,我为了将其加入 STL 并正确执行碰了一鼻子灰,所幸一些后来者就不用做到这么多,现将其公之于众。
1)结构成员对齐扩展到 16 字节
#pragma pack(16)
2)需要被对齐的用户类型,使用下列修饰
#define losALIGNED __declspec(align(16))
所幸的是,用户类型的派生类型乃至上层类型无需使用该修饰即可在堆栈上正确对齐,即满足本身的字节对齐,并依赖于结构成员对齐的定义使得需要对齐的用户类型得于满足。
3)operator new 并不会依循类型需要自动对齐,其唯一的作用就是提供合适大小的空间并调用有关类型的构造式,为此使用下列函数:
inline void* _AlignedMalloc(size_t size)
{
void* p = ::_aligned_malloc(size, 16);
if (!p) std::_Nomemory();
return p;
}
inline void _AlignedFree(void* p)
{
::_aligned_free(p);
}
4)STL 容器的 std::allocator 模板分配器默认使用全局的 operator new 分配合适的空间,所以需要派生一个自定义的分配器来执行对齐,需要实现以下环节:
1、拷贝 std::allocator 当中的有关 typedef
2、拷贝 std::allocator 的所有构造,复制构造、复制赋值实现代码,这些代码块都是留空的,仅满足容器的复制语义,因为存在可 rebind 的 const UserAllocator<_Other> 分配器类型,这是 STL 的一个小技巧,以满足容器类型同分配器类型的一致性
3、拷贝 rebind 结构体,以上拷贝均要注意替换当前分配器类型名
4、重要的一步,实现自定义的分配函数:
#ifdef losSIMD
pointer allocate(size_type _Count)
{
return (pointer)_AlignedMalloc(sizeof(Ty) * _Count);
}
void deallocate(pointer _Ptr, size_type)
{
_AlignedFree(_Ptr);
}
#endif
5)类似 Octree 等特殊容器,将持有指针,指针指向数据被分配到堆空间,毫无疑问,假如以上数据需要满足自身乃至其成员的边界对齐,默认的 operator new 将使用全局的分配函数,为此,需要在用户类型上重载 operator new,这个函数是静态的成员,并且不能被继承,所以每个子类均要在需要满足边界对齐的情况下重载分配函数,为了简化工作,使用了宏来替代:
#ifdef losSIMD
#define AlignedClass(x) void* operator new(size_t size) { return _AlignedMalloc(size); } void* operator new[](size_t size) { return operator new(size); } void* operator new(size_t size, void* ptr) { return ptr; } void operator delete(void* p) { _AlignedFree(p); } void operator delete[](void* p) { operator delete(p); }
#else
#define AlignedClass(x)
#endif
其中涉及到在指定位置分配数据的 operator new 的重载,这段重载不是必需的,因为全局的函数即同样的返回线索地址而不另行分配,这一方法有利于构造式的调用,STL 代码中 _Construct 便是使用该操作符
template<class _T1,
class _T2> inline
void _Construct(_T1 _FARQ *_Ptr, const _T2& _Val)
{ // construct object at _Ptr with value _Val
new ((void _FARQ *)_Ptr) _T1(_Val);
}
这段代码使用 operator new 传入指定类型的处理函数,返回合适的通常是原传入地址后,使用复制构造函数完成初始化,这些都是编译器的行为所决定的,只需知道何时该操作符被调用,以及行为和意义。
6)当用户完成了一些包含特定对齐成员的类型,并且试图通过 operator new 进行创建,有必要在其类型添加这段支持,或者,不要使用 SSE 等扩展指令集。不能指望不做出任何更改边可获得一些灵活的特性。
另一方面,当通过 operator new 创建的用户类型实现了支持特定边界对齐的重载后,其成员及基类型甚至是基类型的成员均满足了结构对齐。
最坏的情况下,试图使用 operator new 来为不包含分配运算符重载的类型执行内存分配以及获得特定边界对齐,可以使用 _AlignedMalloc 以及 _AlignedFree 来管理特定大小的内存,并使用 _Construct(p, Val()); 执行默认构造,更为符合设计美感的方式是派生出一个新的类型,并指定 AlignedClass(x) 宏。
7)使用边界对齐的分配器的实例
std::list< typeStaticSceneElement, AlignedAllocator<typeStaticSceneElement> >
_StaticSceneResource;
std::list< typeDynamicSceneElement, AlignedAllocator<typeDynamicSceneElement> >
_DynamicSceneResource;
类型名不重要,只需要了解到其包含了需要对齐的成员,并且试图存储到 STL 容器,就需要这么做。下面是一个比较糟糕的情况:
typedef std::map<int, Object3D, std::less<int>
, AlignedAllocator<std::pair<const int, Object3D> > > typeDynamicSceneObjectContainer;
8)没有人能预见所有意外的用法,测试,然后找到原因。
9)new[] 以及 delete[] 又将如何?
这是编译器的一项能力,事实上它仍将调用全局的 operator new 以及 operator delete,所以需要在用户类型当中重载这两个符号,传入的需求大小不是用户类型元素数而是实际需要分配的内存大小。编译器知道要为多项元素调用构造和析构,如果使用 _AlignedMalloc 甚至是 new 来创建字节类型内存,需要手动调用构造以及析构,就像这样:
typePointer _Alloc(size_t size_)
{
// 不允许创建空存储区
if (!size_)
throw los::errortypes::bad_alloc_size(t("to allocate SerialUser's dynamic storage, the size is not allow."));
typePointer ptrAlloc = (typePointer)new char[size_ * sizeof(typeElem)];
// 更新特定 SerialUser 的存储区间
_MapDSR[typeDynamicKey(this)]
= typeDynamicRange(uintptr_t(ptrAlloc), uintptr_t(ptrAlloc) + uintptr_t(sizeof(typeElem) * size_));
// 后向填充以便动态区的 SerialUser 类型成员可以获得当前 SerialUser 的登记条目
// 实现上,operator new 将使用特定数据在特定位置执行分配
std::uninitialized_fill_n(ptrAlloc, size_, typeElem());
return ptrAlloc;
}
void _DeAlloc(typePointer ptr_)
{
// 元素存储创建 char 类型动态存储,不会正确执行析构
// 调用 std::_Destroy 以帮助执行析构函数,并使用 delete[] 销毁存储空间
for (size_t idx = 0; idx < _CompactSize(); ++idx)
std::_Destroy(ptr_ + idx);
if (ptr_)
delete[] (char*)ptr_;
// 删除特定 SerialUser 到存储区间的映射
_MapDSR.erase(typeDynamicKey(this));
}
总之,除非你知道你要干什么,否则陷入并不能得到什么好处,只会像上面给出的如此奔放的一段实现代码搞糟了所有的好心情,编译器在确知类型的情况下,其 operator new[] 以及 operator delete[] 版本就会为你做到这么多。
10)糟糕的 std::vector::resize()
这个函数使用了值传递,要么将其修改为常量引用传递的,要么派生自己的类型并且重写 resize 函数。事实上,使用值传递的这位代码写作者可能考虑这个默认值无论何时总是被临时创建用于填充的,担忧部分编译器实现并不保持临时变量,一个不能在退出已传递函数体之前保持有效的临时变量还能指望它能做些什么?
SSE 指令集实现的向量和矩阵运算符实现的摘选:
1)求点乘
float operator *(const Vector3& v) const
{
float r;
#ifdef losSIMD
Vector3 t;
__asm
{
mov esi, this;
mov edi, v;
movaps xmm0, [esi];
mulps xmm0, [edi];
movaps t, xmm0;
}
r = t.x + t.y + t.z;
#else
r = x * v.x + y * v.y + z * v.z;
#endif
return r;
}
2)求叉乘
Vector3 CrossProduct(const Vector3& v) const
{
Vector3 r;
#ifdef losSIMD
__asm
{
mov esi, this;
mov edi, v;
movaps xmm0, [esi];
movaps xmm1, [edi];
movaps xmm2, xmm0;
movaps xmm3, xmm1;
shufps xmm0, xmm0, 0xc9;
shufps xmm1, xmm1, 0xd2;
mulps xmm0, xmm1;
shufps xmm2, xmm2, 0xd2;
shufps xmm3, xmm3, 0xc9;
mulps xmm2, xmm3;
subps xmm0, xmm2;
movaps r, xmm0;
}
#else
r.x = y * v.z - z * v.y;
r.y = z * v.x - x * v.z;
r.z = x * v.y - y * v.x;
#endif
r.w =1.0f;
return r;
}
2)向量和矩阵乘法
inline Vector3 operator *(const Vector3& v, const Matrix& m)
{
Vector3 r;
#ifdef losSIMD
__asm
{
mov esi, v;
mov edi, m;
movaps xmm0, [esi];
movaps xmm1, xmm0;
movaps xmm2, xmm0;
movaps xmm3, xmm0;
shufps xmm0, xmm2, 0x00;
shufps xmm1, xmm2, 0x55;
shufps xmm2, xmm2, 0xaa;
shufps xmm3, xmm3, 0xff;
mulps xmm0, [edi];
mulps xmm1, [edi + 10h];
mulps xmm2, [edi + 20h];
mulps xmm3, [edi + 30h];
addps xmm0, xmm1;
addps xmm0, xmm2;
addps xmm0, xmm3;
movaps r, xmm0;
}
#else
r.x = v.x * m._11 + v.y * m._21 + v.z * m._31 + v.w * m._41;
r.y = v.x * m._12 + v.y * m._22 + v.z * m._32 + v.w * m._42;
r.z = v.x * m._13 + v.y * m._23 + v.z * m._33 + v.w * m._43;
r.w = v.x * m._14 + v.y * m._24 + v.z * m._34 + v.w * m._44;
#endif
return r;
}
3)矩阵间乘法
inline Matrix operator *(const Matrix& m, const Matrix& m1)
{
Matrix mat;
#ifdef losSIMD
__asm
{
mov edi, m1;
movaps xmm4, [edi];
movaps xmm5, [edi + 10h];
movaps xmm6, [edi + 20h];
movaps xmm7, [edi + 30h];
mov esi, m;
mov eax, 0;
loop_tag:
movaps xmm0, [esi + eax];
movaps xmm1, xmm0;
movaps xmm2, xmm0;
movaps xmm3, xmm0;
shufps xmm0, xmm2, 0x00;
shufps xmm1, xmm2, 0x55;
shufps xmm2, xmm2, 0xaa;
shufps xmm3, xmm3, 0xff;
mulps xmm0, xmm4;
mulps xmm1, xmm5;
mulps xmm2, xmm6;
mulps xmm3, xmm7;
addps xmm0, xmm1;
addps xmm0, xmm2;
addps xmm0, xmm3;
movaps [mat + eax], xmm0;
add eax, 16;
cmp eax, 48;
jle loop_tag;
}
#else
for (size_t i = 0; i < 4; ++i)
for (size_t j = 0; j < 4; ++j)
{
float sum = 0;
for (size_t k = 0; k < 4; ++k)
sum += m.val[k] * m1.val[k][j];
mat.val[j] = sum;
}
#endif
return mat;
}
|
|