- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我正在研究一个本地 C++/CLI 类,它执行具有多精度值的整数运算。单个整数由 64 位无符号整数数组表示。符号由 bool 值表示,负值与其绝对值一起存储,而不是作为二进制补码。这使得处理符号问题变得更加容易。目前我正在优化乘法运算。我已经进行了几轮优化,但我的函数仍然需要 的时间是两个 .NET BigInteger 值 的 * 运算符的两倍,这表明进一步优化仍有相当大的潜力。
在寻求帮助之前,让我向您展示我已经尝试过的内容。我的第一次尝试是一种幼稚的方法:使用基本的 64 到 128 位乘法将所有 64 位项目对相乘,然后移位/相加结果。我没有在这里显示代码,因为它非常慢。下一次尝试是 递归分治算法 ,结果证明要好得多。在我的实现中,两个操作数都在中间递归拆分,直到剩下两个 64 位值。这些相乘产生一个 128 位的结果。收集到的基本结果在递归层上一直移位/相加,以产生最终结果。该算法可能受益于需要计算的 64 到 128 位元积少得多的事实,这似乎是主要瓶颈。
所以这是我的代码。第一个片段显示了顶级入口点:
// ----------------------------------------------------------------------------
// Multi-precision multiplication, using a recursive divide-and-conquer plan:
// Left split: (a*2^k + b)i = ai*2^k + bi
// Right split: a(i*2^k + j) = ai*2^k + aj
public: static UINT64* Mul (UINT64* pu8Factor1,
UINT64* pu8Factor2,
UINT64 u8Length1,
UINT64 u8Length2,
UINT64& u8Product)
{
UINT64* pu8Product;
if ((u8Length1 > 0) && (u8Length2 > 0))
{
pu8Product = _SnlMemory::Unsigned ((u8Length1 * u8Length2) << 1);
u8Product = Mul (pu8Product, pu8Factor1, 0, u8Length1,
pu8Factor2, 0, u8Length2);
}
else
{
pu8Product = _SnlMemory::Unsigned (0);
u8Product = 0;
}
return pu8Product;
}
UINT64*
数组指针传入,长度分别指定为相应数组中
UINT64
项的数量。该函数分配了一个足够大的内存块来保存最大预期长度的值,它也用作临时从属结果的暂存器。该函数调用另一个
Mul
函数执行递归评估并返回最终结果实际使用的
UINT64
项的数量。
// ----------------------------------------------------------------------------
// Recursively expand the arbitrary-precision multiplication to the sum of a
// series of elementary 64-to-128-bit multiplications.
private: static UINT64 Mul (UINT64* pu8Product,
UINT64* pu8Factor1,
UINT64 u8Offset1,
UINT64 u8Length1,
UINT64* pu8Factor2,
UINT64 u8Offset2,
UINT64 u8Length2)
{
UINT64 *pu8Lower, u8Lower, *pu8Upper, u8Upper, u8Split;
UINT64 u8Product = 0;
if (u8Length1 > 1)
{
// left split: (a*2^k + b)i = ai*2^k + bi
u8Split = u8Length1 >> 1;
u8Lower = Mul (pu8Lower = pu8Product,
pu8Factor1, u8Offset1, u8Split, // bi
pu8Factor2, u8Offset2, u8Length2);
u8Upper = Mul (pu8Upper = pu8Product + ((u8Split * u8Length2) << 1),
pu8Factor1, u8Offset1 + u8Split, // ai
u8Length1 - u8Split,
pu8Factor2, u8Offset2, u8Length2);
u8Product = Mul (u8Split, pu8Lower, u8Lower, pu8Upper, u8Upper);
}
else if (u8Length2 > 1)
{
// right split: a(i*2^k + j) = ai*2^k + aj
u8Split = u8Length2 >> 1;
u8Lower = Mul (pu8Lower = pu8Product,
pu8Factor1, u8Offset1, u8Length1, // aj
pu8Factor2, u8Offset2, u8Split);
u8Upper = Mul (pu8Upper = pu8Product + ((u8Length1 * u8Split) << 1),
pu8Factor1, u8Offset1, u8Length1, // ai
pu8Factor2, u8Offset2 + u8Split,
u8Length2 - u8Split);
u8Product = Mul (u8Split, pu8Lower, u8Lower, pu8Upper, u8Upper);
}
else // recursion base: 64-to-128-bit multiplication
{
AsmMul1 (pu8Factor1 [u8Offset1],
pu8Factor2 [u8Offset2],
u8Lower, u8Upper);
if (u8Upper > 0)
{
pu8Product [u8Product++] = u8Lower;
pu8Product [u8Product++] = u8Upper;
}
else if (u8Lower > 0)
{
pu8Product [u8Product++] = u8Lower;
}
}
return u8Product;
}
; -----------------------------------------------------------------------------
; 64-bit to 128-bit multiplication, using the x64 MUL instruction
AsmMul1 proc ; ?AsmMul1@@$$FYAX_K0AEA_K1@Z
; ecx : Factor1
; edx : Factor2
; [r8] : ProductL
; [r9] : ProductH
mov rax, rcx ; rax = Factor1
mul rdx ; rdx:rax = Factor1 * Factor2
mov qword ptr [r8], rax ; [r8] = ProductL
mov qword ptr [r9], rdx ; [r9] = ProductH
ret
AsmMul1 endp
// ----------------------------------------------------------------------------
// Shift-add recombination of the results of two partial multiplications.
private: static UINT64 Mul (UINT64 u8Split,
UINT64* pu8Lower,
UINT64 u8Lower,
UINT64* pu8Upper,
UINT64 u8Upper)
{
FLAG fCarry;
UINT64 u8Count, u8Lower1, u8Upper1;
UINT64 u8Product = u8Lower;
if (u8Upper > 0)
{
u8Count = u8Split + u8Upper;
fCarry = false;
for (u8Product = u8Split; u8Product < u8Count; u8Product++)
{
u8Lower1 = u8Product < u8Lower ? pu8Lower [u8Product] : 0;
u8Upper1 = pu8Upper [u8Product - u8Split];
if (fCarry)
{
pu8Lower [u8Product] = u8Lower1 + u8Upper1 + 1;
fCarry = u8Lower1 >= MAX_UINT64 - u8Upper1;
}
else
{
pu8Lower [u8Product] = u8Lower1 + u8Upper1;
fCarry = u8Lower1 > MAX_UINT64 - u8Upper1;
}
}
if (fCarry)
{
pu8Lower [u8Product++] = 1;
}
}
return u8Product;
}
_umul128
和
_addcarry_u64
编译器内在函数的计算能力。代码非常简单:
public: static UINT64* Mul (UINT64* pu8Factor1,
UINT64* pu8Factor2,
UINT64 u8Length1,
UINT64 u8Length2,
UINT64& u8Product)
{
u8Product = u8Length1 + u8Length2;
CHAR c1Carry1, c1Carry2;
UINT64 u8Offset, u8Offset1, u8Offset2, u8Item1, u8Item2, u8Lower, u8Upper;
UINT64* pu8Product = _SnlMemory::Unsigned (u8Product);
if (u8Product > 0)
{
for (u8Offset1 = 0; u8Offset1 < u8Length1; u8Offset1++)
{
u8Offset = u8Offset1;
u8Item1 = pu8Factor1 [u8Offset1];
u8Item2 = 0;
c1Carry1 = 0;
c1Carry2 = 0;
for (u8Offset2 = 0; u8Offset2 < u8Length2; u8Offset2++)
{
u8Lower = _umul128 (u8Item1, pu8Factor2 [u8Offset2], &u8Upper);
c1Carry1 = _addcarry_u64 (c1Carry1, u8Item2, u8Lower,
&u8Item2);
c1Carry2 = _addcarry_u64 (c1Carry2, u8Item2,
pu8Product [u8Offset],
pu8Product + u8Offset);
u8Item2 = u8Upper;
u8Offset++;
}
if (c1Carry1 != 0)
{
c1Carry2 = _addcarry_u64 (c1Carry2, u8Item2 + 1,
pu8Product [u8Offset],
pu8Product + u8Offset);
}
else if (u8Item2 != 0)
{
c1Carry2 = _addcarry_u64 (c1Carry2, u8Item2,
pu8Product [u8Offset],
pu8Product + u8Offset);
}
}
if (pu8Product [u8Product - 1] == 0)
{
u8Product--;
}
}
return pu8Product;
}
_umul128
的两个波纹进位加法流进行基本的 64-zo-128 位
_addcarry_u64
乘法。这个版本的性能是迄今为止我尝试过的最好的!它比等效的 .NET BigInteger 乘法快 10 倍左右,所以最终我实现了 20 倍的加速。
最佳答案
正如我们在 reference source 中看到的那样,.NET 中的 BigInteger 使用相当慢的乘法算法,通常使用 32x32->64 乘法的二次时间算法。但它是用低开销编写的:迭代、很少分配,并且不调用不可内联的 ASM 过程。部分产品会立即添加到结果中,而不是单独实现。
不可内联的 ASM 过程可以替换为 _umul128固有的。手动进位计算(条件 +1
和确定输出进位)可以替换为 _addcarry_u64
固有的。
更高级的算法,例如 Karatsuba 乘法和 Toom-Cook 乘法可能是有效的,但当递归一直执行到单肢级别时就不行了——这远远超过了开销超过保存的基本乘法的点。作为一个具体的例子,this implementation Java 的 BigInteger 的 80 个肢体切换到 Karatsuba(2560 位,因为它们使用 32 位肢体),以及 240 个肢体的 3-way Toom-Cook。考虑到 80 的阈值,只有 64 条肢体,我不会期望那里有太多的 yield ,如果有的话。
关于algorithm - 哪个是多精度乘法最有效的算法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55336761/
我在一本书(Interview Question)中读到这个问题,想在这里详细讨论这个问题。请点亮它。 问题如下:- 隐私和匿名化 马萨诸塞州集团保险委员会早在 1990 年代中期就有一个绝妙的主意
我最近接受了一次面试,面试官给了我一些伪代码并提出了相关问题。不幸的是,由于准备不足,我无法回答他的问题。由于时间关系,我无法向他请教该问题的解决方案。如果有人可以指导我并帮助我理解问题,以便我可以改
这是我的代码 public int getDist(Node root, int value) { if (root == null && value !=0) return
就效率而言,Strassen 算法应该停止递归并应用乘法的最佳交叉点是多少? 我知道这与具体的实现和硬件密切相关,但对于一般情况应该有某种指南或某人的一些实验结果。 在网上搜索了一下,问了一些他们认为
我想学习一些关于分布式算法的知识,所以我正在寻找任何书籍推荐。我对理论书籍更感兴趣,因为实现只是个人喜好问题(我可能会使用 erlang(或 c#))。但另一方面,我不想对算法进行原始的数学分析。只是
我想知道你们中有多少人实现了计算机科学的“ classical algorithms ”,例如 Dijkstra's algorithm或现实世界中的数据结构(例如二叉搜索树),而不是学术项目? 当有
我正在解决旧编程竞赛中的一些示例问题。在这个问题中,我们得到了我们有多少调酒师以及他们知道哪些食谱的信息。制作每杯鸡尾酒需要 1 分钟,我们需要使用所有调酒师计算是否可以在 5 分钟内完成订单。 解决
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 8 年前。 Improve
我开始学习 Nodejs,但我被困在中间的某个地方。我从 npm 安装了一个新库,它是 express -jwt ,它在运行后显示某种错误。附上代码和错误日志,请帮助我! const jwt = re
我有一个证书,其中签名算法显示“sha256rsa”,但指纹算法显示“sha1”。我的证书 SHA1/SHA2 的标识是什么? 谢谢! 最佳答案 TL;TR:签名和指纹是完全不同的东西。对于证书的强度
我目前在我的大学学习数据结构类(class),并且在之前的类(class)中做过一些算法分析,但这是我在之前的类(class)中遇到的最困难的部分。我们现在将在我的数据结构类(class)中学习算法分
有一个由 N 个 1x1 方格组成的区域,并且该区域的所有部分都是相连的(没有任何方格无法到达的方格)。 下面是一些面积的例子。 我想在这个区域中选择一些方块,并且两个相邻的方块不能一起选择(对角接触
我有一些多边形形状的点列表,我想将其包含在我页面上的 Google map 中。 我已经从原始数据中删除了尽可能多的不必要的多边形,现在我剩下大约 12 个,但它们非常详细以至于导致了问题。现在我的文
我目前正在实现 Marching Squares用于计算等高线曲线,我对此处提到的位移位的使用有疑问 Compose the 4 bits at the corners of the cell to
我正在尝试针对给定算法的约束满足问题实现此递归回溯函数: function BACKTRACKING-SEARCH(csp) returns solution/failure return R
是否有包含反函数的库? 作为项目的一部分,我目前正在研究测向算法。我正在使用巴特利特相关性。在 Bartlett 相关性中,我需要将已经是 3 次矩阵乘法(包括 Hermitian 转置)的分子除以作
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 这个问题似乎与 help center 中定义的范围内的编程无关。 . 关闭 8 年前。 Improve
问题的链接是UVA - 1394 : And There Was One . 朴素的算法是扫描整个数组并在每次迭代中标记第 k 个元素并在最后停止:这需要 O(n^2) 时间。 我搜索了一种替代算法并
COM 中创建 GUID 的函数 (CoCreateGUID) 使用“分散唯一性算法”,但我的问题是,它是什么? 谁能解释一下? 最佳答案 一种生成 ID 的方法,该 ID 具有一定的唯一性保证,而不
在做一个项目时我遇到了这个问题,我将在这个问题的实际领域之外重新措辞(我想我可以谈论烟花的口径和形状,但这会使理解更加复杂).我正在寻找一种(可能是近似的)算法来解决它。 我有 n 个不同大小的容器,
我是一名优秀的程序员,十分优秀!