【问题标题】:Why does my data not seem to be aligned?为什么我的数据似乎没有对齐?
【发布时间】:2011-02-27 20:34:31
【问题描述】:

我试图弄清楚如何最好地预先计​​算一些正弦和余弦值,将它们存储在对齐的块中,然后将它们用于 SSE 计算:

在我的程序开始时,我创建了一个包含成员的对象:

static __m128 *m_sincos;

然后我在构造函数中初始化该成员:

m_sincos = (__m128*) _aligned_malloc(Bins*sizeof(__m128), 16);
for (int t=0; t<Bins; t++)
  m_sincos[t] = _mm_set_ps(cos(t), sin(t), sin(t), cos(t));



我在使用m_sincos的时候遇到了三个问题:
- 数据似乎没有对齐

movaps xmm0, m_sincos[t] //crashes
movups xmm0, m_sincos[t] //does not crash

-变量似乎不正确

movaps result, xmm0 // returns values that are not what is in m_sincos[t]
//Although, putting a watch on m_sincos[t] displays the correct values

-真正让我感到困惑的是,这使一切正常(但太慢了):

__m128 _sincos = m_sincos[t];
movaps xmm0, _sincos
movaps result, xmm0

【问题讨论】:

  • 我刚刚做了一个 sizeof(__m128)、sizeof(m_sincos[t]) 和 sizeof(result),它们都返回“16”
  • 使用_mm_malloc而不是_aligned_malloc似乎功能完全一样,而且就内存位置而言,当我初始化m_sincos时,地址是:0x260e2720,然后当我去使用它时,地址是仍然是 0x260e2720... 这有帮助吗?
  • hmm,尝试在每次加载之前放置 assert 语句以检查对齐而不是组装,您可以尝试 _mm_load_ps 内在函数吗?
  • @aaa - 使用 _mm_store_ps(result,m_sincos[t]);这工作正常,并返回适当的值!至于检查对齐的断言语句,你只是想让我检查 sizeof(m_sincos[t]) 吗?因为我正在这样做并且它们总是大小为 16,但我真的很好奇为什么我不能使用汇编代码,内在和汇编之间的区别是什么?
  • 这种重复似乎是个糟糕的主意,除非您的表非常小,或者您反复使用内存中的相同__m128。执行[c, s] 的 8B 负载并将其随机播放以获取[c, s, s, c] 非常便宜,并且可能会通过减少缓存未命中来收回成本。与让编译器进行索引相比,内联 asm 只是 bad (因此它可以在循环中将某些内容增加 16)。如果您使用 asm,请在 asm 中编写循环或函数,而不是片段。

标签: c++ alignment intrinsics


【解决方案1】:

m_sincos[t] 是一个 C 表达式。然而,在汇编指令 (__asm?) 中,它被解释为 x86 寻址模式,结果完全不同。比如VS2008 SP1编译:

movaps xmm0, m_sincos[t]

into:(调试模式下应用崩溃时查看反汇编窗口)

movaps xmm0, xmmword ptr [t]

该解释试图将存储在变量t 的地址处的128 位值复制到xmm0 中。但是,t 是一个 32 位值,地址可能未对齐。执行该指令可能会导致对齐失败,并且在 t 的地址对齐的奇怪情况下会得到不正确的结果。

您可以通过使用适当的 x86 寻址模式来解决此问题。这是缓慢但清晰的版本:

__asm mov eax, m_sincos                  ; eax <- m_sincos
__asm mov ebx, dword ptr t
__asm shl ebx, 4                         ; ebx <- t * 16 ; each array element is 16-bytes (128 bit) long
__asm movaps xmm0, xmmword ptr [eax+ebx] ; xmm0 <- m_sincos[t]

旁注:

当我把它放在一个完整的程序中时,会发生一些奇怪的事情:

#include <math.h>
#include <tchar.h>
#include <xmmintrin.h>

int main()
{
    static __m128 *m_sincos;
    int Bins = 4;

    m_sincos = (__m128*) _aligned_malloc(Bins*sizeof(__m128), 16);
    for (int t=0; t<Bins; t++) {
        m_sincos[t] = _mm_set_ps(cos((float) t), sin((float) t), sin((float) t), cos((float) t));
        __asm movaps xmm0, m_sincos[t];
        __asm mov eax, m_sincos
        __asm mov ebx, t
        __asm shl ebx, 4
        __asm movaps xmm0, [eax+ebx];
    }

    return 0;
}

当你运行它时,如果你留意寄存器窗口,你可能会注意到一些奇怪的东西。尽管结果是正确的,xmm0 在执行movaps 指令之前获得了正确的值。这是怎么发生的?

查看生成的汇编代码可知_mm_set_ps()将sin/cos结果加载到xmm0,然后将其保存到内存地址m_sincos[t]。但是xmm0 中的值也仍然存在。 _mm_set_ps 是“内在”,而不是函数调用;它不会在完成后尝试恢复它使用的寄存器的值。

如果可以从中吸取教训,可能是在使用 SSE 内部函数时,请始终使用它们,以便编译器可以为您优化。否则,如果您使用的是内联汇编,也请始终使用它。

【讨论】:

  • 哇,这可能是我在所有搜索中读到的最佳答案,感谢您的清晰解释!那么,如果我想在整个过程中使用汇编,这是否意味着我必须像使用内在函数一样执行 shl 指令才能移动到数组中的正确位置?非常感谢!!
  • 是的,您需要将数组索引乘以 16 才能得到正确的偏移量。 x86 有许多寻址模式,可以为您增加索引,避免显式移位。然而,我找不到一个能乘以 16 的。并不意味着没有一个,只是我没有找到它。另一种方法是在每次迭代时将索引增加 16。
【解决方案2】:

您应该始终使用 instrinsics,甚至只是将其打开并保留它们,而不是显式对其进行编码。这是因为 __asm 不能移植到 64 位代码。

【讨论】:

  • 感谢您的建议,您发帖时我正在阅读!
猜你喜欢
  • 2013-03-05
  • 2018-02-15
  • 2013-03-13
  • 1970-01-01
  • 2018-03-05
  • 1970-01-01
  • 2020-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多