几年前我需要一种方法用Cuda做一些基本的128位整数运算: 128 bit integer on cuda?。 现在我遇到了同样的问题,但这次我需要在32位嵌入式系统(英特尔爱迪生)上运行一些基本的128位算术(总和,位移和乘法),它不支持任何类型的128位。但是,直接支持64位整数(unsigned long long int)。
我天真地尝试使用上次在CPU上回答的asm代码,但是我遇到了一堆错误。我对asm真的没有经验,所以:有64位整数的最有效方法是在128位中实现加法,乘法和位移?
答案 0 :(得分:4)
更新:由于OP尚未接受答案< hint>< hint>,我附加了更多代码。
使用上面讨论的库可能是一个好主意。虽然您今天可能只需要一些功能,但最终您可能会发现还需要一个功能。然后又一次。直到最终你最终编写,调试和维护自己的128位数学库。这是浪费你的时间和精力。
那就是说。如果您决定自己动手:
1)你之前问过的cuda问题已经有了乘法的c代码。它有问题吗?
2)转移可能不会从使用asm中受益,所以c解决方案对我来说也是有意义的。 虽然如果性能确实是一个问题,我会看看Edison是否支持SHLD / SHRD,可能使这一点更快。否则,m 也许这样的方法?
my_uint128_t lshift_uint128 (const my_uint128_t a, int b)
{
my_uint128_t res;
if (b < 32) {
res.x = a.x << b;
res.y = (a.y << b) | (a.x >> (32 - b));
res.z = (a.z << b) | (a.y >> (32 - b));
res.w = (a.w << b) | (a.z >> (32 - b));
} elseif (b < 64) {
...
}
return res;
}
更新:由于Edison可能支持SHLD / SHRD,因此可能的替代方案比上面的“c”代码更高效。与声称速度更快的所有代码一样,您应该对其进行测试。
inline
unsigned int __shld(unsigned int into, unsigned int from, unsigned int c)
{
unsigned int res;
if (__builtin_constant_p(into) &&
__builtin_constant_p(from) &&
__builtin_constant_p(c))
{
res = (into << c) | (from >> (32 - c));
}
else
{
asm("shld %b3, %2, %0"
: "=rm" (res)
: "0" (into), "r" (from), "ic" (c)
: "cc");
}
return res;
}
inline
unsigned int __shrd(unsigned int into, unsigned int from, unsigned int c)
{
unsigned int res;
if (__builtin_constant_p(into) &&
__builtin_constant_p(from) &&
__builtin_constant_p(c))
{
res = (into >> c) | (from << (32 - c));
}
else
{
asm("shrd %b3, %2, %0"
: "=rm" (res)
: "0" (into), "r" (from), "ic" (c)
: "cc");
}
return res;
}
my_uint128_t lshift_uint128 (const my_uint128_t a, unsigned int b)
{
my_uint128_t res;
if (b < 32) {
res.x = a.x << b;
res.y = __shld(a.y, a.x, b);
res.z = __shld(a.z, a.y, b);
res.w = __shld(a.w, a.z, b);
} else if (b < 64) {
res.x = 0;
res.y = a.x << (b - 32);
res.z = __shld(a.y, a.x, b - 32);
res.w = __shld(a.z, a.y, b - 32);
} else if (b < 96) {
res.x = 0;
res.y = 0;
res.z = a.x << (b - 64);
res.w = __shld(a.y, a.x, b - 64);
} else if (b < 128) {
res.x = 0;
res.y = 0;
res.z = 0;
res.w = a.x << (b - 96);
} else {
memset(&res, 0, sizeof(res));
}
return res;
}
my_uint128_t rshift_uint128 (const my_uint128_t a, unsigned int b)
{
my_uint128_t res;
if (b < 32) {
res.x = __shrd(a.x, a.y, b);
res.y = __shrd(a.y, a.z, b);
res.z = __shrd(a.z, a.w, b);
res.w = a.w >> b;
} else if (b < 64) {
res.x = __shrd(a.y, a.z, b - 32);
res.y = __shrd(a.z, a.w, b - 32);
res.z = a.w >> (b - 32);
res.w = 0;
} else if (b < 96) {
res.x = __shrd(a.z, a.w, b - 64);
res.y = a.w >> (b - 64);
res.z = 0;
res.w = 0;
} else if (b < 128) {
res.x = a.w >> (b - 96);
res.y = 0;
res.z = 0;
res.w = 0;
} else {
memset(&res, 0, sizeof(res));
}
return res;
}
3)添加可能受益于asm。你可以试试这个:
struct my_uint128_t
{
unsigned int x;
unsigned int y;
unsigned int z;
unsigned int w;
};
my_uint128_t add_uint128 (const my_uint128_t a, const my_uint128_t b)
{
my_uint128_t res;
asm ("addl %5, %[resx]\n\t"
"adcl %7, %[resy]\n\t"
"adcl %9, %[resz]\n\t"
"adcl %11, %[resw]\n\t"
: [resx] "=&r" (res.x), [resy] "=&r" (res.y),
[resz] "=&r" (res.z), [resw] "=&r" (res.w)
: "%0"(a.x), "irm"(b.x),
"%1"(a.y), "irm"(b.y),
"%2"(a.z), "irm"(b.z),
"%3"(a.w), "irm"(b.w)
: "cc");
return res;
}
我刚刚将其删除,因此请自担风险。我没有爱迪生,但这适用于x86。
更新:如果您只是进行积累(请考虑to += from
而不是上面的代码c = a + b
),此代码可能会为您提供更好的服务:
inline
void addto_uint128 (my_uint128_t *to, const my_uint128_t from)
{
asm ("addl %[fromx], %[tox]\n\t"
"adcl %[fromy], %[toy]\n\t"
"adcl %[fromz], %[toz]\n\t"
"adcl %[fromw], %[tow]\n\t"
: [tox] "+&r"(to->x), [toy] "+&r"(to->y),
[toz] "+&r"(to->z), [tow] "+&r"(to->w)
: [fromx] "irm"(from.x), [fromy] "irm"(from.y),
[fromz] "irm"(from.z), [fromw] "irm"(from.w)
: "cc");
}
答案 1 :(得分:2)
如果使用外部库是一个选项,请查看this question。您可以使用TTMath这是一个非常简单的标题,用于大精度数学。在32位体系结构ttmath:UInt<4>
上将创建一个128位int
类型,其中包含四个32位肢体。
如果您必须自己编写,那么SO上已经有很多解决方案,我会在这里总结一下
对于加法和减法,它非常简单直接,只需添加/减去单词(大型int库通常称为肢体)从最低有效到高有效当然还有随身携带。
typedef struct INT128 {
uint64_t H, L;
} my_uint128_t;
inline my_uint128_t add(my_uint128_t a, my_uint128_t b)
{
my_uint128_t c;
c.L = a.L + b.L;
c.H = a.H + b.H + (c.L < a.L); // c = a + b
return c;
}
检查装配输出
编译器已经可以为双字操作生成有效的代码,但是许多人在使用高级语言编译多字操作时使用“with with carry”并不够智能,如问题所示{{3 }}。因此,使用上面的2 long long
将使它不仅更具可读性,而且更容易让编译器发出更高效的代码。
如果仍然不符合您的性能要求,则必须使用内在函数或在汇编中写入。要将bignum
中存储的128位值添加到{eax,ebx,ecx,edx}中的128位值,可以使用以下代码
add edx, [bignum]
adc ecx, [bignum+4]
adc ebx, [bignum+8]
adc eax, [bignum+12]
Clang
的等效内在值为efficient 128-bit addition using carry flagunsigned *x, *y, *z, carryin=0, carryout;
z[0] = __builtin_addc(x[0], y[0], carryin, &carryout);
carryin = carryout;
z[1] = __builtin_addc(x[1], y[1], carryin, &carryout);
carryin = carryout;
z[2] = __builtin_addc(x[2], y[2], carryin, &carryout);
carryin = carryout;
z[3] = __builtin_addc(x[3], y[3], carryin, &carryout);
您需要将内在函数更改为编译器支持的内在函数,例如like this或__builtin_uadd_overflow
in gcc MSVC和ICC
有关更多信息,请阅读这些
对于位移,您可以在问题Bitwise shift operation on a 128-bit number中找到C解决方案。这是一个简单的左移,但您可以展开递归调用以获得更高的性能
_addcarry_u32
可以在问题128-bit shifts using assembly language?
中找到小于32位移位的程序集void shiftl128 (
unsigned int& a,
unsigned int& b,
unsigned int& c,
unsigned int& d,
size_t k)
{
assert (k <= 128);
if (k >= 32) // shifting a 32-bit integer by more than 31 bits is "undefined"
{
a=b;
b=c;
c=d;
d=0;
shiftl128(a,b,c,d,k-32);
}
else
{
a = (a << k) | (b >> (32-k));
b = (b << k) | (c >> (32-k));
c = (c << k) | (d >> (32-k));
d = (d << k);
}
}
可以类似地实施右移,或者只是从上面链接的问题中复制
乘法和除法要复杂得多,您可以在问题Efficient Multiply/Divide of two 128-bit Integers on x86 (no 64-bit)中引用解决方案:
shld edx, ecx, cl
shld ecx, ebx, cl
shld ebx, eax, cl
shl eax, cl
您还可以使用128bit代码
找到许多相关问题