【问题标题】:SSE optimized emulation of 64-bit integersSSE 优化的 64 位整数仿真
【发布时间】:2012-04-30 18:48:54
【问题描述】:

对于我正在从事的业余项目,我需要在 x86 CPU 上模拟某些 64 位整数运算,并且它需要快速

目前,我正在通过 MMX 指令执行此操作,但这真的很痛苦,因为我必须一直刷新 fp 寄存器状态(而且因为大多数 MMX 指令处理 已签名 整数,我需要无符号行为)。

所以我想知道这里的 SSE/优化专家是否可以使用 SSE 提出更好的实现。

我需要的操作如下(非常具体):

uint64_t X, Y;

X = 0;
X = 1;
X << 1;
X != Y;
X + 1;
X & 0x1 // get lsb
X | 0x1 // set lsb
X > Y;

具体来说,我不需要通用的加法或移位,例如,只需加一和左移一。真的,只是这里显示的精确操作。

当然,除了在 x86 上,uint64_t 是通过使用两个 32 位标量来模拟的,这很慢(而且,在我的情况下,根本不起作用,因为我需要加载/存储是原子的,在加载/存储两个单独的寄存器时它们不会是这样)。

因此,我需要 SIMD 解决方案。 其中一些操作很简单,SSE2 已经支持。其他人(!=&lt;)需要更多的工作。

建议? SSE 和 SSE2 都很好。允许 SSE3 需要一些说服力,而 SSE4 可能是不可能的(支持 SSE4 的 CPU 很可能运行 64 位无论如何,所以我不需要这些变通办法)

【问题讨论】:

  • SSE2 直接支持 64 位整数加法。我假设您还需要 64 位乘法? 64 x 64 -> 64 位(下半部分),还是需要 64 x 64 -> 128 位?
  • 不需要乘法,只是我上面显示的特定操作(所以甚至不是一般的加法,只是增加 1。是的,加法是由 SSE2 提供的,但我想我不妨只是为了完整起见,显示我需要的所有操作。只是意味着其中一些很简单:)
  • 如果您使用的 CPU 不能处理 64 位但支持 SSE2,那么这将是 Athlon XP、Pentium III 或更旧的 Pentium IV。在 Athlon XP 的情况下,我根本不会期望任何性能提升,因为它确实将每个 SSE 操作拆分为两个 64 位操作,然后分别执行。对于 Pentium III - 我不知道。对于 Pentium IV,您可能会获得一些加速 - 取决于从和向通用注册器传输的频率,因为这些在此硬件上的速度非常慢。
  • @drhirsch 我不知道你想表达什么观点。你只是因为无聊而吹毛求疵吗?是的,我知道操作系统不限制可用的 SSE 指令集。而我自己的机器是运行在 64 位操作系统上的 i7。但我希望我的代码也能在其他计算机上运行,​​包括那些由于操作系统或 CPU 限制为 32 位代码的计算机。并且依靠,比如说,SSE4.2 将切断大多数 32 位计算机。依靠 SSE2 几乎可以涵盖所有这些。现在,你有什么相关的要贡献的吗?
  • 你为什么不这样写你的问题?就目前而言,听起来您需要在 CPU 上进行 64 位操作,而该 CPU able 无法在 64 位模式下运行 - 有点旧。

标签: c++ optimization x86 64-bit sse


【解决方案1】:

SSE2 直接支持一些 64 位整数运算:

将两个元素都设置为 0:

__m128i z = _mm_setzero_si128();

将两个元素都设置为 1:

__m128i z = _mm_set1_epi64x(1);      // also works for variables.
__m128i z = _mm_set_epi64x(hi, lo);  // elements can be different

__m128i z = _mm_set_epi32(0,1,0,1);  // if any compilers refuse int64_t in 32-bit mode.  (None of the major ones do.)

设置/加载低 64 位,零扩展至 __m128i

// supported even in 32-bit mode, and listed as an intrinsic for MOVQ
// so it should be atomic on aligned integers.
_mm_loadl_epi64((const __m128i*)p);     // movq or movsd 64-bit load

_mm_cvtsi64x_si128(a);      // only ICC, others refuse in 32-bit mode
_mm_loadl_epi64((const __m128i*)&a);  // portable for a value instead of pointer

基于_mm_set_epi32 的东西可能会被一些编译器编译成一团糟,所以_mm_loadl_epi64 似乎是跨 MSVC 和 ICC 以及 gcc/clang 的最佳选择,实际上应该是安全的,可以满足您对 atomic 的要求在 32 位模式下加载 64 位。见the Godbolt compiler explorer

垂直加/减每个 64 位整数:

__m128i z = _mm_add_epi64(x,y)
__m128i z = _mm_sub_epi64(x,y)

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_integer_arithmetic.htm#intref_sse2_integer_arithmetic

左移:

__m128i z = _mm_slli_epi64(x,i)   // i must be an immediate

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_int_shift.htm

位运算符:

__m128i z = _mm_and_si128(x,y)
__m128i z = _mm_or_si128(x,y)

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_integer_logical.htm

SSE 没有增量,因此您必须使用带有 1 的常量。


比较更难,因为在 SSE4.1 pcmpeqq 和 SSE4.2 pcmpgtq 之前没有 64 位支持

这是平等的:

__m128i t = _mm_cmpeq_epi32(a,b);
__m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));

这会将每个 64 位元素设置为 0xffffffffffff(如果它们相等,则称为 -1)。如果您希望它作为 int 中的 01,您可以将其拉出使用_mm_cvtsi32_si128()并添加1。(但有时您可以使用total -= cmp_result;而不是转换和添加。)

And Less-Than:(未完全测试)

a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
__m128i t = _mm_cmplt_epi32(a,b);
__m128i u = _mm_cmpgt_epi32(a,b);
__m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);

如果a 中的对应元素小于b,这会将每个64 位元素设置为0xffffffffffff


这是返回布尔值的“等于”和“小于”的版本。它们返回底部 64 位整数的比较结果。

inline bool equals(__m128i a,__m128i b){
    __m128i t = _mm_cmpeq_epi32(a,b);
    __m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));
    return _mm_cvtsi128_si32(z) & 1;
}
inline bool lessthan(__m128i a,__m128i b){
    a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
    b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
    __m128i t = _mm_cmplt_epi32(a,b);
    __m128i u = _mm_cmpgt_epi32(a,b);
    __m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
    z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);
    return _mm_cvtsi128_si32(z) & 1;
}

【讨论】:

  • 我刚刚更新了“小于”代码。不是 100% 确定它是否正确。
  • 我为 VS2010 写了一个“小于”案例here 的快速测试。可能需要一些调整才能在其他编译器上运行。 0x00000000 &lt; 0x00000001 失败(除非我在测试中犯了错误)
  • 它对我有用。您的链接显示它甚至没有编译。还是我错过了什么?
  • @jalf,我发现你的测试出了什么问题。您需要z.m128i_u32[0] == 0xffffffffull 而不是z.m128i_u64[0] == 0xffffffffull。 (32 位而不是 64 位)但无论如何,我已经用返回 bools 的函数更新了我的答案。
  • 我刚刚对小于代码进行了另一项更改。显然,比较运算符假定输入是有符号的。所以我不得不插入一对xors 来反转最高位来解决这个问题。在这一点上,它看起来很糟糕,所以最好使用uint64_t 并查看编译器生成什么。
猜你喜欢
  • 2013-07-25
  • 1970-01-01
  • 2015-02-22
  • 2012-10-04
  • 2021-08-15
  • 2017-02-05
  • 2016-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多