内存没有正确对齐?
Posted
技术标签:
【中文标题】内存没有正确对齐?【英文标题】:memory not aligned properly? 【发布时间】:2013-06-06 18:40:17 【问题描述】:我正在尝试在 SSE 中使用对齐操作,但遇到了问题(惊喜)。
typedef struct _declspec(align(16)) Vec4
float x;
float y;
float z;
float w;
;
Vec4 SSE_Add(const Vec4 &a, const Vec4 &b)
_declspec(align(16)) Vec4 return_val;
_asm
MOV EAX, a // Load pointers into CPU regs
MOV EBX, b
MOVAPS XMM0, [EAX] // Move unaligned vectors to SSE regs
MOVAPS XMM1, [EBX]
ADDPS XMM0, XMM1 // Add vector elements
MOVAPS [return_val], XMM0 // Save the return vector
return return_val;
我在return return_val
收到访问冲突。这是对齐问题吗?我该如何纠正?
【问题讨论】:
使用未对齐的存储是否正常工作? “这是对齐问题吗?” - 我能想象的唯一原因是您的编译器无法正确对齐堆栈,现在不应该案子。你的编译器和相应的标志是什么? 附带说明,请注意自己的理智、代码的清晰性、程序的可移植性和您的应用程序的性能,而不是使用 instrinsics 进行 SSE 操作而不是内联装配和手动对齐属性。 @LeeJacobs 如果已经进行了分析,那么只需在您的测试中包含内在函数,看看会发生什么。但一般来说,编译器知道内在函数,并且他完全了解它们的作用,因此他完全有可能对它们进行重新排序和优化,甚至可以跨多个函数进行内联(甚至我也很惊讶 VS2010 由什么漂亮的代码组成一堆包装成多个函数的 SSE 内在函数)。另一方面,汇编块对于编译器来说是一个完整的黑匣子,任何优化都完全取决于您。 @LeeJacobs Intrinsics 通常会提高性能,因为它们让编译器负责着色寄存器和调度指令以避免流水线气泡,这是机器可以最佳执行的机械任务。 (与许多其他任务不同,聪明人比愚蠢的编译器做得更好。)您可以在此处了解有关该过程的更多信息:csl.cornell.edu/courses/ece4750/handouts/… 【参考方案1】:我发现问题出在 EBX 寄存器上。如果您推送/弹出 EBX,那么它可以工作。我不知道为什么,所以如果有人可以解释这一点 - 请做。
编辑:我查看了反汇编,并在函数的开头将堆栈指针存储在 EBX 中:
mov ebx, esp
所以你最好确保不要丢失它。
【讨论】:
听起来像答案,弄乱堆栈指针就足以在 return 语句中创建访问冲突疯狂。 (让您远离内联汇编的另一个原因。) 这是首选内部函数而不是内联汇编的一个重要原因:它避免了无意中破坏编译器用于其他用途的寄存器。相反,如果您打算使用汇编,则应牢记平台的 ABI 及其所有调用约定。 保存堆栈指针是函数序言的一部分,它是为了制作一个堆栈帧。它使函数激活记录可在固定地址访问(在本例中为 ebp+something)。但是,通常是 EBP 寄存器具有这个作用......所以......这有点编译器特定的。【参考方案2】:这有点依赖于编译器...不是正确的写法: movaps return_val, xmm0
为什么不向我们展示生成的代码?
你写这篇文章的方式比让编译器自己做要糟糕得多。
这个函数应该是可内联的,并且在最好的情况下翻译成一条指令,如果你这样写它就不能被内联。 此函数可以在 Intel 64 的寄存器中接收其参数并将其结果返回到寄存器中,如果您这样编写它,您将强制使用堆栈。 此函数可以使用返回值优化,这样编写会强制您将 xmm0 写入必须再次复制的 return_val 变量。所以...对齐与未对齐的 MOVPS 是您最不关心的问题。
为什么不直接用可移植的代码写:
inline void add(const float *__restrict__ a, const float *__restrict__ b, float *__restrict__ r)
for (int i = 0; i != 4; ++i) r[i] = a[i] + b[i];
【讨论】:
或者,同样可移植,但实际上针对 SSE 进行了优化,代码_mm_store_ps(r, _mm_add_ps(_mm_load_ps(a), _mm_load_ps(b)))
。我想知道编译器是否真的会将该循环转换为 SSE 指令(嗯,也许是 Intel 的)。
当它使用__restrict__
时,我不会称这段代码为“可移植的”
@Crashworks:具有内在函数的版本在没有任何修饰符的情况下执行得很好,因为它在进行任何写入之前将先前的值复制到 MMX/SSE 寄存器中,它不必担心如果 source 会发生什么和目的地重叠。
@BenVoigt Intriniscs 根本不可移植——它们特定于指令集。
@Crashworks:在针对该指令集但由不同供应商制造的编译器中,名称可能相同,也可能不同。所以是的,不便携。以上是关于内存没有正确对齐?的主要内容,如果未能解决你的问题,请参考以下文章