假设我有一个分配器,它为每个请求提供整个缓存行。 因此每次分配都将从一个新的缓存行开始。
constexpr auto L1 = std::hardware_destructive_interference_size;
std::byte* allocator_cachelines = static_cast<std::byte*>(
::operator new(TOTAL_CACHELINES*L1, std::align_val_t{L1}));
// ALL TYPES USING THE ALLOCATOR HAVE IMPLICIT LIFETIME
// (are trivially copyable and destructable)
struct ABC { int i=0; };
struct XYZ { float f=1.f; };
auto* abc = reinterpret_cast<ABC*>(allocator.allocate(ABC_COUNT * sizeof(ABC)));
// allocate returns enough cachelines from allocator_cachelines
// to fulfill the request,
// plus the bookkeeping to mark those cachelines as in use
std::cout << abc[0].i; // OK?
// since ABC is the first type stored within the memory,
// does it's implicit lifetime begin?
现在,如果该内存被释放并重新用于另一个隐式生命周期类型,会发生什么。
allocator.deallocate(abc);
// deallocate marks the returned abc cachelines as free for reuse
auto* xyz = reinterpret_cast<XYZ*>(allocator.allocate(XYZ_COUNT * sizeof(XYZ)));
// allocate returns the abc cachelines which were just freed
std::cout << xyz[0].f; // OK?
// or do I need to first do
xyz[0] = XYZ{};
std::cout << xyz[0].f; // OK NOW?
此外,如果对分配器的访问由 std::mutex 控制。 这会解决任何严格的别名违规问题吗? 由于互斥体引入了内存栅栏,内存重新排序不会导致严格别名的冲突,因为分配和解除分配为重用内存提供了互斥类型(假设指向先前数据类型的指针没有被缓存以供以后使用)。
最后,如果我有一个从分配器分配的浮点数数组并将其与 SIMD 内联函数一起使用。
我是否必须写入所有元素以避免严格的别名违规?
由于 SIMD 本质可以访问数组末尾从未写入的元素。
例如
浮点数组:[0,1,2,3]
如果 0、1、2 被写入而 3 未被写入(并且另一种类型之前已经占用了这些字节)。
4 个浮点的任何 SIMD 固有负载都会将 3 作为与上次写入的类型不同的类型进行访问。
我将你的问题分为三个部分:
ABC
的生命周期是否隐式开始?是,只要
ABC
是隐式生命周期类(确实如此)。但是,您必须注意,除非运行其构造函数,否则不会使用隐式成员初始值设定项 (i = 0
),因此,i
将获得不确定的值。
如果要初始化
ABC
对象,正确的语法是:
new (abc[0]) ABC();
不是
abc[0] = ABC();
不。这不是严格的别名规则的目的。仅当您有两个(基本上)不同类型的指针指向同一对象并同时使用它们时,才会违反它。如果您在某一时刻停止使用其中一种并开始使用另一种,那也没关系。例如。即使这样也可以:
T* t = new T;
... // use t
t->T::~T(); // destruct
U* u = new (t) U; // construct U in the place formerly occupied by T
// provided that U fits into memory occupied by T
... // use u, CAN'T use t
u->U::~U(); // now destruct U
::operator delete(u);
不。即使在访问无关元素时,您也可以自由使用 SIMD 指令,只要这些额外元素在内存中不与其他数据重叠(根据您的描述,这种情况不会发生)。