内存没有正确对齐?

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:在针对该指令集但由不同供应商制造的编译器中,名称可能相同,也可能不同。所以是的,不便携。

以上是关于内存没有正确对齐?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的卡在 bootstrap 5 中没有正确对齐?

[前端设计] 设置float:left后居然没有正确对齐?

UITableView 没有正确对齐

有没有更简洁的方法让 html 列全部对齐=正确?

jqGrid在屏幕布局中没有正确对齐。缺什么?

Vulkan UniformBuffer 内存对齐不正确 [重复]