我使用了很多SSE编译器内在函数编写了一个3D矢量类.一切正常,直到我开始实现具有3D矢量作为新成员的类.我在发布模式下经历了奇怪的崩溃,但在调试模式下却没有,反之亦然.
所以我读了一些文章,并认为我需要将拥有3D矢量类实例的类对齐到16个字节.所以我只是在类之前添加了_MM_ALIGN16(__declspec(align(16)),如下所示:
_MM_ALIGN16 struct Sphere
{
// ....
Vector3 point;
float radius
};
Run Code Online (Sandbox Code Playgroud)
这似乎首先解决了这个问题.但在更改了一些代码后,我的程序又开始以奇怪的方式崩溃.我在网上搜索了一些,发现了一篇博客文章.我尝试了作者Ernst Hot为解决这个问题做了什么,它对我也有用.我在我的类中添加了new和delete运算符,如下所示:
_MM_ALIGN16 struct Sphere
{
// ....
void *operator new (unsigned int size)
{ return _mm_malloc(size, 16); }
void operator delete (void *p)
{ _mm_free(p); }
Vector3 point;
float radius
};
Run Code Online (Sandbox Code Playgroud)
恩斯特提到这种方法也可能存在问题,但他只是链接到一个不再存在的论坛,而没有解释为什么它可能会有问题.
所以我的问题是:
定义运算符有什么问题?
为什么不添加_MM_ALIGN16足够的类定义?
处理SSE内在函数的对齐问题的最佳方法是什么?
当从连续的内存位置执行一系列_mm_stream_load_si128()调用(MOVNTDQA)时,硬件预取器是否仍会启动,或者我应该使用显式软件预取(使用NTA提示)以获得预取的好处,同时仍然避免缓存污染?
我问这个的原因是因为他们的目标似乎与我相矛盾.流加载将获取绕过缓存的数据,而预取器尝试主动将数据提取到缓存中.
当顺序迭代一个大型数据结构(处理过的数据不会在很长一段时间内被修饰)时,我有必要避免污染chache层次结构,但我不想因频繁出现频繁的~100次循环处罚-fetcher闲置.
目标架构是Intel SandyBridge
通常有两种类型的SIMD指令:
A.使用对齐的内存地址的那些,如果地址未在操作数大小边界上对齐,则会引发一般保护(#GP)异常:
movaps xmm0, xmmword ptr [rax]
vmovaps ymm0, ymmword ptr [rax]
vmovaps zmm0, zmmword ptr [rax]
Run Code Online (Sandbox Code Playgroud)
B.以及使用未对齐内存地址的那些,不会引发此类异常:
movups xmm0, xmmword ptr [rax]
vmovups ymm0, ymmword ptr [rax]
vmovups zmm0, zmmword ptr [rax]
Run Code Online (Sandbox Code Playgroud)
但是我只是好奇,为什么我要用脚射击自己并使用第一组的对齐记忆指令呢?
在对涉及 的代码进行基准测试时std::optional<double>,我注意到 MSVC 生成的代码的运行速度大约是 clang 或 gcc 生成的代码的一半。在花了一些时间减少代码后,我注意到 MSVC 显然在为std::optional::operator=. 使用std::optional::emplace()不会表现出速度减慢。
以下功能
void test_assign(std::optional<double> & f){
f = std::optional{42.0};
}
Run Code Online (Sandbox Code Playgroud)
产生
sub rsp, 24
vmovsd xmm0, QWORD PTR __real@4045000000000000
mov BYTE PTR $T1[rsp+8], 1
vmovups xmm1, XMMWORD PTR $T1[rsp]
vmovsd xmm1, xmm1, xmm0
vmovups XMMWORD PTR [rcx], xmm1
add rsp, 24
ret 0
Run Code Online (Sandbox Code Playgroud)
注意未对齐的 mov 操作。相反,函数
void test_emplace(std::optional<double> & f){
f.emplace(42.0);
}
Run Code Online (Sandbox Code Playgroud)
编译为
mov rax, 4631107791820423168 ; 4045000000000000H
mov BYTE PTR [rcx+8], 1
mov …Run Code Online (Sandbox Code Playgroud) _mm_load_ps() SSE 内在函数被定义为对齐的,如果地址未对齐则抛出异常。然而,视觉工作室似乎生成了未对齐的读取。
由于并非所有编译器都是相同的,这隐藏了错误。如果能够打开实际的对齐操作,那就太好了,尽管以前的性能影响似乎不再存在了。
换句话说,编写代码:
__m128 p1 = _mm_load_ps(data);
Run Code Online (Sandbox Code Playgroud)
目前生产:
movups xmm0,xmmword ptr [eax]
Run Code Online (Sandbox Code Playgroud)
预期结果:
movaps xmm0,xmmword ptr [eax]
Run Code Online (Sandbox Code Playgroud)
(我是被微软要求来这里问的)
sse ×4
c++ ×2
intrinsics ×2
visual-c++ ×2
x86 ×2
alignment ×1
avx ×1
avx512 ×1
cpu-cache ×1
performance ×1
prefetch ×1
simd ×1
stdoptional ×1
x86-64 ×1