我从三个值A,B,C(无符号32位整数)开始。而且我必须获得两个值D,E(也是无符号32位整数)。其中
D = high(A*C);
E = low(A*C) + high(B*C);
我期望两个32位uint的乘法产生64位结果。 "高"和"低"只是我用来标记前32位和最后32位的64位乘法的结果。
我尝试获取一些全功能的优化代码。我在庞大的循环中有一小部分代码,这只是很少的命令行,但是它几乎消耗了所有的计算时间(几小时计算的物理模拟)。这就是为什么我尝试对这个小部分进行优化的原因,其余代码可以保持更多"用户安排良好"。
有一些SSE指令适合计算提到的例程。 gcc编译器可能会优化工作。但是,如果有必要,我不会拒绝直接在SSE intructions中编写一些代码的选项。
请耐心等待我对SSE的低经验。我将尝试象征性地为SSE编写算法。订购面具或理解结构可能会有一些错误。
A*C
的乘法和B*C
的乘法,并返回两个64位值。A*C
的64位结果,R,S是64位结果B*C
。然后我继续在寄存器中重新排列值为P,Q,0,R 此算法应返回E和D的正确值。
我的问题:
c ++中是否有静态代码生成类似于SSE算法的SSE例程?我喜欢更高性能的解决方案。如果算法对标准c ++命令有问题,那么如何在SSE中编写算法呢?
我使用TDM-GCC 4.9.2 64位编译器。
(注意:问题在建议后被修改)
(注2:我对此http://sci.tuomastonteri.fi/programming/sse有一个灵感,就是使用SSE来获得更好的性能)
答案 0 :(得分:1)
如果我理解正确,您想要计算A * B中潜在溢出的数量。如果是,那么你有两个不错的选择 - "使用两倍大变量" (为uint64写128位数学函数 - 它并不那么难(或者等我明天发布)),并且使用浮点类型": (浮点型(A)*浮子(B))/浮动(C) 因为精度损失是最小的(假设float是4个字节,双8个字节,长16个字节长),float和uint32都需要4个字节的内存(对于uint64_t使用double,因为它应该是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
但如果你想要真正准确和正确的答案,我建议使用两倍大的计算类型
现在我想起来了 - 你可以使用long double for uint64和double for uint32而不是为uint64编写函数,但我不认为它保证long double将是128bit,并且你必须检查一下。我会选择更普遍的选择。
编辑:
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)
(必须放在&#34;代码&#34;因为我在这里是新的,并且无法找到另一种方法来阻止格式化脚本吃掉其中的一半)
这只是一个简单的想法。你必须检查它的正确性(对不起,但我现在真的很累 - 但结果不应该在任何计算点溢出,因为最大值可能有价值如果我是正确的,则为2X,并且算法本身似乎很好。)
如果您仍然需要帮助,我将在明天编写代码。
答案 1 :(得分:1)
除非有多个输入并行处理,否则不需要向量。 clang和gcc已经很好地优化了编写代码的“正常”方式:转换为两倍大小,乘以,然后转换为高半。编译器认识到这种模式。
他们注意到操作数最初为32位,因此在转换为64b之后,上半部分全部为零。因此,他们可以使用x86的mul
insn进行32b * 32b-> 64b乘法,而不是进行完全扩展精度64b乘法。在64位模式下,他们使用__uint128_t
版本的代码执行相同的操作。
Both of these functions compile to fairly good code (one mul
or imul
per multiply).。 gcc -m32
不支持128b类型,但我不会介绍它,因为1.你只询问32位值的完全乘法,以及2.当你想要快速运行时,你应该总是使用64位代码。如果你在结果不适合寄存器的情况下进行全乘法,则clang将避免许多额外的mov指令because gcc is silly about this。这个小测试函数为gcc bug报告提供了一个很好的测试用例。
该godbolt链接包含一个在循环中调用它的函数,将结果存储在数组中。它通过一堆混洗自动矢量化,但如果你有多个输入并行处理,它仍然看起来像加速。在乘法后,不同的输出格式可能需要较少的混洗,例如可能为D
和E
存储单独的数组。
我包含128b版本,表明编译器可以处理这个问题,即使它不是微不足道的(例如,只需执行64位imul
指令就可以在32位输入上进行64 * 64-> 64b乘法运算,在将任何可能位于函数入口上的输入寄存器中的高位置零后。)
当针对Haswell CPU及更新版本时,gcc和clang可以使用mulx
BMI2指令。 (我在godbolt链接中使用-mno-bmi2 -mno-avx2
来保持asm更简单。如果你拥有 Haswell CPU,只需使用-O3 -march=haswell
。)mulx dest1, dest2, src1
dest1:dest2 = rdx * src1
mul src1
1}}而rdx:rax = rax * src1
执行mulx
。因此edx
有两个只读输入(一个隐式:rdx
/ mov
)和两个只写输出。这使得编译器可以使用较少的mul
指令进行全乘,以便将数据输入和输出mulx
的隐式寄存器。这只是一个小的加速,尤其是。因为64位// 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
在Haswell上有4个循环延迟而不是3个循环延迟。 (Strangely, 64bit mul
and mulx
are slightly cheaper than 32bit mul
and mulx
。)
{{1}}