SSE指令导致普通浮点运算返回-1.#INV

7
我正在编写一个SSE方法,用于音频处理,但遇到了问题。我已经根据英特尔的论文实现了基于SSE的随机函数:

http://software.intel.com/en-us/articles/fast-random-number-generator-on-the-intel-pentiumr-4-processor/

此外,我还有一个使用SSE进行从Float到S16转换的方法,转换方法如下:

unsigned int Float_S16LE(float *data, const unsigned int samples, uint8_t *dest)
{
  int16_t *dst = (int16_t*)dest;
  const __m128 mul = _mm_set_ps1((float)INT16_MAX);
   __m128 rand;
  const uint32_t even = count & ~0x3;
  for(uint32_t i = 0; i < even; i += 4, data += 4, dst += 4)
  {
    /* random round to dither */
    FloatRand4(-0.5f, 0.5f, NULL, &rand);

    __m128 rmul = _mm_add_ps(mul, rand);
    __m128 in = _mm_mul_ps(_mm_load_ps(data),rmul);
    __m64 con = _mm_cvtps_pi16(in);

    memcpy(dst, &con, sizeof(int16_t) * 4);
  }
}

FloatRand4的定义如下:

static inline void FloatRand4(const float min, const float max, float result[4], __m128 *sseresult = NULL)
{
  const float delta  = (max - min) / 2.0f;
  const float factor = delta / (float)INT32_MAX;
  ...
}

如果sseresult != NULL,则返回__m128结果,而result未使用。 这在第一个循环中表现得完美,但在下一个循环中,delta变成了-1.#INF,而不是1.0。如果我注释掉__m64 con = _mm_cvtps_pi16(in);这一行,问题就解决了。 我认为FPU进入了一个未知状态或其他什么状态。

_mm_cvtps_pi16 这种方法不是一个好主意。使用 _mm_cvtps_epi32、_mm_packs_epi32 以及 _mm_store_si128/_mm_storeu_si128 的组合来将 8 个浮点数转换为 8 个 int16_t,这样你的问题就解决了! - Marat Dukhan
2个回答

9

混合使用SSE整数算术和(常规)浮点数计算。由于两者都在同一寄存器上操作,因此可能会产生奇怪的结果。如果您使用:

_mm_empty()

当 FPU 处于正确状态时,需要进行重置。微软提供了何时使用 EMMS 的指南


1
这不仅仅是因为_mm_cvtps_pi16吗?我认为_mm_empty只适用于MMX。所以我会替换掉它,因为据我所知_mm_empty的代价很高。 - Sam
是的,更正确的解决方案是放弃那些FPU指令,一直使用SSE,但这是正确的答案,因为它解释了为什么会发生这种情况。 - Geoffrey

1
  • _mm_load_ps不能保证进行对齐加载。float*数据可能对齐到4个字节而不是16个字节 => _mm_loadu_ps
  • 使用memcpy可能会破坏使用SSE所获得的优势,应该使用__m64的存储命令,但同样要注意对齐。 如果无法对__m64进行非对齐流或存储,则可以将其保留在_m128i内,并使用_mm_maskmoveu_si128进行掩码写入,或手动存储这8个字节。

http://msdn.microsoft.com/en-us/library/bytwczae.aspx


谢谢您的建议,我应该说明样例中省略了对齐代码,所有传递给该方法的数据都已对齐。 - Geoffrey
1
我考虑使用uint8_t数组[8]的联合来手动复制。但是这种构造(以及memcpy)总会存在“存储到加载”的问题。因此,将__int64(或其中两个)传输到128位寄存器中,并分别执行_mm_maskmoveu_si128或_mm_stream*应该更有效率。流式传输避免了输出缓存污染,这可能很有用,因为一旦写入,您不需要立即再次使用它。 - Sam
1
是的,但如果您现在打算坚持使用__m64,您可以使用_mm_extract_pi16(SSE2)来提取单词0-3。 - Sam
1
另一种方法是保留两个输出寄存器,使用 _mm_cvtps_epi32 将 2 x 4float 转换为 int32,然后使用 _mm_packs_epi32 将两者打包成一个向量。然后您就可以在一个 128bit 寄存器中存储/流式传输 8 个有符号的 16bit 样本。 - Sam
1
我选择使用 _mm_extract_epi16 方法,因为缓冲区可能没有足够的值进行 2 x 4float 转换,并且 __m64 内部函数在 x64 处理器上不受支持。哦,而且我可以轻松地使用 con = _mm_or_si128(_mm_slli_epi16(con, 8), _mm_srli_epi16(con, 8)); 进行大小端交换。 - Geoffrey
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接