得到整数的高半部分和低半乘
Getting the high half and low half of a full integer multiply
我以三个值 A,B,C(无符号 32 位整数(开头。我必须获得两个值 D,E(也是无符号的 32 位整数(。哪里
D = high(A*C);
E = low(A*C) + high(B*C);
我希望两个 32 位 uint 的乘以会产生 64 位结果。"高"和"低"只是我用 64 位结果标记前 32 位和后 32 位的 64 位。
我尝试获得一些 allready 功能代码的优化代码。我在巨大的循环中有一小段代码,它只有几个命令行,但它消耗了几乎所有的计算时间(几个小时计算的物理模拟(。这就是为什么我尝试优化这一小部分的原因,其余代码可以保持更"用户良好安排"。
有一些 SSE 指令适合上述计算例程。gcc编译器可能会做优化的工作。但是,如果有必要,我不拒绝直接在 SSE 入侵中编写一些代码的选项。
请耐心等待我对SSE的低经验。我将尝试以符号方式为 SSE 编写算法。订购口罩或理解结构可能会有一些错误。
- 将四个 32 位整数按顺序存储到一个 128 位寄存器中:A、B、C、C。
- 将指令(可能是 pmuludq(应用到上述 128 位寄存器中,该寄存器将 32 位整数对相乘并返回 64 位整数对作为结果。因此,它应该同时计算
A*C
乘法和B*C
乘法并返回两个 64 位值。 - 我希望我有新的 128 位寄存器值 P,Q,R,S(四个 32 位块(,其中 P,Q 是
A*C
的 64 位结果,R,S 是B*C
的 64 位结果。然后我继续将寄存器处的值重新排列为顺序 P,Q,0,R
取第一个 64 位 - P,Q 并添加第二个 64 位 0,R。结果是一个新的 64 位值。
- 将结果的前 32 位读取为 D,将结果的最后 32 位读取为 E。
此算法应返回正确的 E 和 D 值。
我的问题:
c++ 中是否有静态代码可以生成与上述 1-5 SSE 算法类似的 SSE 例程?我选择具有更高性能的解决方案。如果算法对于标准 c++ 命令有问题,有没有办法在 SSE 中编写算法?
我使用 TDM-GCC 4.9.2 64 位编译器。
(注:问题经建议后修改(
(注2:我从这个 http://sci.tuomastonteri.fi/programming/sse 中得到了使用SSE以获得更好性能的灵感(
除非有多个输入要并行处理,否则不需要向量。 Clang和GCC在优化编写代码的"正常"方式方面已经做得很好:投射到两倍的大小,乘以,然后移位以获得高半。 编译器可识别此模式。
他们注意到操作数开始时为 32bit,因此在转换为 64b 后,上半部分全部为零。 因此,他们可以使用 x86 的 mul
insn 进行 32b*32b->64b 乘法,而不是执行完全扩展精度的 64b 乘法。 在 64 位模式下,它们对__uint128_t
版本的代码执行相同的操作。
这两个函数都可以编译成相当好的代码(每个乘法一个mul
或imul
(。 gcc -m32
不支持 128b 类型,但我不会讨论它,因为 1.您只询问了 32 位值的全乘法和 2。当您希望某些内容快速运行时,您应该始终使用 64 位代码。 如果你正在做全乘法,结果不适合寄存器,clang会避免很多额外的mov指令,因为gcc对此很愚蠢。 这个小测试函数为 gcc 错误报告提供了一个很好的测试用例。
该 godbolt 链接包含一个在循环中调用 this 的函数,将结果存储在数组中。 它通过一堆随机播放自动矢量化,但如果有多个输入要并行处理,它看起来仍然像加速。 不同的输出格式在乘法后可能会减少洗牌,例如可能存储单独的数组用于D
和E
。
我包括 128b 版本是为了表明编译器可以处理这个问题,即使它不是微不足道的(例如,只需执行 64 位imul
指令即可在 32 位输入上执行 64*64->64b 乘法,在将函数输入时可能位于输入寄存器中的任何高位归零后。
当针对 Haswell CPU 及更新版本时,gcc 和 clang 可以使用 mulx
BMI2 指令。 (我在 godbolt 链接中使用了-mno-bmi2 -mno-avx2
来保持 asm 更简单。 如果你有一个哈斯韦尔CPU,只需使用-O3 -march=haswell
。 mulx dest1, dest2, src1
dest1:dest2 = rdx * src1
,而mul src1
rdx:rax = rax * src1
。 所以mulx
有两个只读输入(一个隐式:edx
/rdx
(,和两个只写输出。 这使编译器可以使用更少的mov
指令执行全乘法,以将数据传入和传出mul
的隐式寄存器。 这只是一个小的加速,特别是因为 64 位mulx
在 Haswell 上有 4 个周期延迟而不是 3 个周期延迟。 (奇怪的是,64位mul
和mulx
比32位mul
和mulx
略便宜。
// compiles to good code: you can and should do this sort of thing:
#include <stdint.h>
struct DE { uint32_t D,E; };
struct DE f_structret(uint32_t A, uint32_t B, uint32_t C) {
uint64_t AC = A * (uint64_t)C;
uint64_t BC = B * (uint64_t)C;
uint32_t D = AC >> 32; // high half
uint32_t E = AC + (BC >> 32); // We could cast to uint32_t before adding, but don't need to
struct DE retval = { D, E };
return retval;
}
#ifdef __SIZEOF_INT128__ // IDK the "correct" way to detect __int128_t support
struct DE64 { uint64_t D,E; };
struct DE64 f64_structret(uint64_t A, uint64_t B, uint64_t C) {
__uint128_t AC = A * (__uint128_t)C;
__uint128_t BC = B * (__uint128_t)C;
uint64_t D = AC >> 64; // high half
uint64_t E = AC + (BC >> 64);
struct DE64 retval = { D, E };
return retval;
}
#endif
如果我理解正确,您希望计算 A*B 中潜在溢出的数量。如果是,那么您有 2 个不错的选择 - "使用两倍大的变量"(为 uint64 编写 128 位数学函数 - 这并不难(或等待我明天发布((和"使用浮点类型":(浮点数(A(*浮点数(B((/浮点数(C(由于精度损失最小(假设浮点数为 4 字节,双精度 8 字节,长双精度 16 字节长(,并且浮点数和 uint32 都需要 4 字节的内存(使用双精度表示uint64_t因为它应该是 8 字节长(:
#include <iostream>
#include <conio.h>
#include <stdint.h>
using namespace std;
int main()
{
uint32_t a(-1), b(-1);
uint64_t result1;
float result2;
result1 = uint64_t(a)*uint64_t(b)/4294967296ull; // >>32 would be faster and less memory consuming
result2 = float(a)*float(b)/4294967296.0f;
cout.precision(20);
cout<<result1<<'n'<<result2;
getch();
return 0;
}
生产:
4294967294
4294967296
但是如果你想要真正精确和正确的答案,我建议使用两倍的大类型进行计算。
现在我想到了 - 你可以对 uint64 使用长双精度,对uint32 使用双精度,而不是为 uint64 编写函数,但我认为不能保证长双精度是 128 位,你必须检查它。我会选择更通用的选择。
编辑:
You can write function to calculate that without using anything more
than A, B and result variable which would be of the same type as A.
Just add rightmost bit of (where Z equals B*(A>>pass_number&1)) Z<<0,
Z<<1, Z<<2 (...) Z<<X in first pass, Z<<-1, Z<<0, Z<<1 (...) Z<<(X-1)
for second (there should be X passes), while right shifting the result
by 1 (the just computed bit becomes irrelevant to us after it's
computed as it won't participate in calculation anymore, and it would
be erased anyway after dividing by 2^X (doing >>X)
(不得不放入"代码",因为我是新来的,找不到另一种方法来防止格式化脚本吃掉一半(
这只是一个快速的想法。您必须检查它的正确性(对不起,但我现在真的很累 - 但结果不应该在任何计算点溢出,因为如果我是正确的,最大进位的值将是 2X,并且算法本身似乎很好(。
如果您仍然需要帮助,我明天会为此编写代码。
- 如何反转整数参数包
- enum是C++中的宏变量还是整数变量
- 努力将整数转换为链表。不知道我在这里做错了什么
- 整数不会重复超过随机数
- 在C++中手动调整数组大小
- SSE2包装的8位整数签名乘数(高半):将M128i(16x8位)分解为两个M128i(每个8x16),然后重新包装
- 获取两个无符号整数 C++ 乘积的高 32 位的有效方法
- 从低到高组织整数并在C++中使用结构
- 有效的方法来计算浮子后下一个更高的整数
- 如何在不使用循环的情况下将低字节和高字节的字符数组转换为它们的整数数组
- 如何将 32 位有符号整数放入更高的 32 位 64 位无符号整数中
- CEIL对于确切的整数部门来说太高了
- 如何从64位整数获得更高的20位
- 获取 64 位整数乘法的高部分
- 得到整数的高半部分和低半乘
- 将高分辨率时钟时间转换为整数(Chrono)
- 将整数提升为高幂
- 写入wav[PMC]的音频缓冲区整数会产生高噪声
- Main函数返回将不会返回高整数
- 大整数除法 - 高德纳算法 D