加法效率与操作数的大小成正比?

时间:2013-07-29 08:09:42

标签: performance

刚刚突然问到我的问题,我不认为我在这里看到了答案。二进制加法算法所花费的时间是否与操作数的大小成比例?

显然,添加110101101010101010110101010110100101010010101的时间会比1 + 1更长,但我的问题更多地涉及较小的值。是否存在可忽略的差异,没有差异,理论差异?

在什么时候,通过这些基本的计算,我们应该开始研究更有效的计算方法吗?即:通过与大指数进行平方来计算巨大的权力。

3 个答案:

答案 0 :(得分:5)

我们如何看待二进制模式......

1101011010101010101101010 (大)
10110100101010010101 (中)
1 (小)

32位计算机如何看待二进制模式...

00000001101011010101010101101010 32位,
00000000000010110100101010010101 32位,
00000000000000000000000000000001 我很喜欢

在32位系统上,以上所有数字都需要添加相同的时间(CPU指令数)。因为它们都适合基本的计算模块,即32位CPU寄存器。


16位计算机如何看待二进制模式...

 1
+1 = ?

0000000000000001 我很喜欢
0000000000000001 我很喜欢

 00000001101011010101010101101010
+00000000000010110100101010010101 = ?

00000001101011010101010101101010 对我来说太大了!
00000000000010110100101010010101 对我来说太大了!

在16位系统上,由于较大的数字不适合16位寄存器,因此需要额外传递(以添加在添加前16LSB之后剩余的有效位)。

步骤1:添加最低有效位
0101010101101010
0100101010010101

步骤2:添加其余部分(记住上次操作中的进位)
000000000000000C
0000000110101101
0000000000001011


  

我们可以开始考虑优化数学运算   数字一旦数字不再适合基本计算单位   系统,即CPU寄存器。

开发现代硬件架构时要牢记这一点并支持SIMD指令。当编译器看到这样的情况,即在32位系统上运行128位解密逻辑时,编译器通常会使用它们(在x86上 SSE ,在ARM上 NEON )。

此外,结果的大小不是仅检查操作数的大小,而是确定系统是否可以在一个步骤内完成数学运算。不仅涉及操作数,还需要考虑正在执行的操作。

例如,在32位系统上,使用常规操作可以绝对添加两个30位数字,因为结果保证不会超过32位寄存器。但是,将相同的两个30位数相乘可能会导致数字不适合32位。

如果没有能够将结果存储在单个计算单元中的保证,为了确保所有可能值的结果的有效性,架构(和编译器)必须:

  • 走很长的路,即多步数学运算
  • 采用SIMD优化
  • 定义和实现自定义机制
    (比如寄存器对 EDX:EAX 将结果保存在x86上)

答案 1 :(得分:2)

实际上,添加适合处理器单词的不同整数之间没有(或完全可以忽略不计),因为它应该始终是固定时间操作。

理论上,添加两个无符号整数的复杂度应该是O(log(n)),其中n是两者中较大的一个。因此,在添加成为问题之前,你需要走得很高。

至于在简单和复杂算法之间用于计算数字的界限,我没有确切的答案。但是,我想到了GMP库。根据我的理解,他们仔细选择了他们的算法,并在什么情况下在性能方面使用它们。你可能想看看他们做了什么。

答案 2 :(得分:0)

我有点不同意上述答案。这在很大程度上取决于具体情况。

对于简单的整数运算(对于循环计数器等),然后在64位机器上,计算将使用64位通用寄存器(RSI / RCX /等)完成。在这些情况下,8位或64位添加之间的速度没有差异。

如果你正在处理整数数组,并假设编译器能够很好地优化代码,那么是的,更小更快(但不是你认为的原因)。

在AVX2指令集中,您可以访问4个整数加法指令:

__m256i _mm256_add_epi8 (__m256i a, __m256i b); // 32 x 8bit
__m256i _mm256_add_epi16(__m256i a, __m256i b); // 16 x 16bit
__m256i _mm256_add_epi32(__m256i a, __m256i b); // 8 x 32bit
__m256i _mm256_add_epi64(__m256i a, __m256i b); // 4 x 64bit

您会注意到它们一次只能运行256位,这意味着如果您使用的是64位,则可以处理4个整数加法,而如果使用8位整数则可以处理32位加法。 (如上所述,您需要确保您有足够的精度)。它们都需要相同数量的时钟周期来计算 - 1clk。

使用较小的数据类型还有其他影响,主要是更好的CPU缓存使用率和减少的内存读/写次数。

然而,回到原始问题的逐位计算。在新的AVX-512指令集之前,它可能看起来有点傻。但是,新指令集包含三元逻辑指令。使用此指令,可以非常容易地计算任意位长度的512个加法。

inline __m512i add(__m512i x, __m512i x, __m512i carry_in)
{
  return _mm512_ternarylogic_epi32(carry_in, y, x, 0x96);
}
inline __m512i adc(__m512i x, __m512i x, __m512i carry_in)
{
  return _mm512_ternarylogic_epi32(carry_in, y, x, 0xE8);
}

__m512i A[NUM_BITS];
__m512i B[NUM_BITS];
__m512i RESULT[NUM_BITS];
__m512i CARRY = _mm512_setzero_ps();

for(int i = 0; i < NUM_BITS; ++i)
{
  RESULT[i] = add(A[i], B[i], CARRY);
  CARRY = adc(A[i], B[i], CARRY);
}

在这个特定的例子中(说实话,现实世界的使用可能非常有限!),执行512次添加所花费的时间确实与NUM_BITS成正比。