我正在尝试使用以下代码在CUDA中实现大整数添加
__global__ void add(unsigned *A, unsigned *B, unsigned *C/*output*/, int radix){
int id = blockIdx.x * blockDim.x + threadIdx.x;
A[id ] = A[id] + B[id];
C[id ] = A[id]/radix;
__syncthreads();
A[id] = A[id]%radix + ((id>0)?C[id -1]:0);
__syncthreads();
C[id] = A[id];
}
但它不能正常工作,我现在也不知道如何处理额外的进位。感谢
答案 0 :(得分:1)
TL; DR 构建一个进位超前加法器,其中每个加法器都添加模数基数,而不是模数2
你模型中的问题是你有一个涟漪。请参阅Rippling carry adders。
如果您在FPGA中不会出现问题,因为他们有专门的逻辑来快速完成(carry chains, they're cool)。但是,唉,你在GPU上了!
也就是说,对于给定的id
,当只有较小{{1}的所有总和时,您只知道输入进位(因此您是要求A[id]+B[id]
还是A[id]+B[id]+1
})已经计算了值。事实上,最初,你只知道第一次携带。
id
每个总和也有一个进位输出,它不在图纸上。因此,您必须将此较大方案中的添加视为具有3个输入和2个输出的函数: A[3]+B[3] + ? A[2]+B[2] + ? A[1]+B[1] + ? A[0]+B[0] + 0
| | | |
v v v v
C[3] C[2] C[1] C[0]
为了不等待O(n)完成总和(其中n是您的总和被切入的项目数),您可以预先计算每个(C, c_out) = add(A, B, c_in)
的所有可能结果。由于id
和A
不会改变,所以这并不是很大的工作量。因此,您有两种可能的输出:B
和(c_out0, C) = add(A, B, 0)
。
现在有了所有这些结果,我们需要基本实现carry lookahead unit。
为此,我们需要弄清楚每个和的进位输出(c_out1, C') = add(A, B, 1)
和P
的函数:
G
a.k.a.以下所有定义
P
c_out1 && !c_out0
A + B == radix-1
a.k.a.以下所有定义
G
c_out1 && c_out0
c_out0
所以换句话说,A + B >= radix
。所以现在我们有一个算法的开始,它可以很容易地告诉我们每个id作为其进位输入函数的进位输出:
c_out = G or (P and c_in)
,计算id
C[id] = A[id]+B[id]+0
G[id] = C[id] > radix -1
现在我们可以在O(log(n))中完成,即使在GPU上看起来很糟糕,但仍然比等待更短。实际上,我们可以从彼此相邻的两个新增内容中获得一个组P[id] = C[id] == radix-1
和一个组G
:
P
和id
:
id+1
step = 2
if id % step == 0, do steps 6 through 10, otherwise, do nothing
group_P = P[id] and P[id+step/2]
group_G = (P[id+step/2] and G[id]) or G[id+step/2]
c_in[id+step/2] = G[id] or (P[id] and c_in[id])
step = step * 2
最后(在每次使用if step < n, go to 5
次更少的树的每个级别重复步骤5-10之后),所有内容都将以id
和P
的形式表示您计算的s和G
c_in[0]
。在维基百科页面上有分组的公式,而不是2,这将得到O(log_4(n))而不是O(log_2(n))的答案。
因此算法结束:
0
,获取id
c_in[id]
我们在最后一部分中真正做到的是模仿带有逻辑的进位超前加法器的电路。但是,我们已经在硬件中添加了类似的东西(根据定义)。
让我们根据基于(C[id]+c_in[id]) % radix
的基数替换我们对P
和G
的定义,就像我们硬件中的逻辑一样,模仿2位的总和2
每个阶段a
:b
(xor)和P = a ^ b
(逻辑和)。换句话说,G = a & b
和a = P or G
。因此,如果我们创建一个b = G
整数和一个intP
整数,其中每个位分别是intG
和P
,我们从每个G
总和计算出来的(限制)我们为64和),然后加法id
具有与我们精心设计的逻辑方案完全相同的进位传播。
我认为减少形成这些整数仍然是一个对数运算,但这是可以预期的。
有趣的是,总和的每个位都是其进位输入的函数。实际上,总和的每一位最终都是3位(intP | intG) + intG
的函数。
a+b+c_in % 2
,则P == 1
,a + b == 1
a+b+c_in % 2 == !c_in
为a+b
或0
,2
因此,我们可以简单地形成a+b+c_in % 2 == c_in
int_cin = ((P|G)+G) ^ P
为^
的整数(或位阵)xor
。
因此,我们的算法有一个替代结尾,取代了步骤4及其后的步骤:
id
P
和G
id
:P = P << id
和G = G << id
intG
和intP
OR
P
和G
id
0 ..63 int_cin = ((P|G)+G) ^ P
id
,得到`c_in = int_cin&amp; (1&lt; id)? 1:0; (C[id]+c_in) % radix
PS:另外,如果radix
很大,请注意数组中的整数溢出。如果它不是那么整个事情真的没有意义我猜...
PPS:在备用结尾中,如果您有超过64个项目,请按P
和G
来表征,就好像radix
是2^64
一样,并且在更高级别执行相同的步骤(减少,获得c_in
),然后返回到较低级别,7
P+G+carry in from higher level