C++ Fast dot产品适用于非常特殊的情况
给定一个大小为L的向量X,其中X的每个标量元素都来自一个二元集合{0,1},如果大小为L的向量Y由整数值元素组成,则需要找到点积z=点(X,Y)。我建议,必须有一个非常快速的方法来做到这一点 假设我们有C++ Fast dot产品适用于非常特殊的情况,c++,c,algorithm,math,C++,C,Algorithm,Math,给定一个大小为L的向量X,其中X的每个标量元素都来自一个二元集合{0,1},如果大小为L的向量Y由整数值元素组成,则需要找到点积z=点(X,Y)。我建议,必须有一个非常快速的方法来做到这一点 假设我们有L=4;X[L]={1,0,0,1};Y[L]={-4,2,1,0}我们必须找到z=X[0]*Y[0]+X[1]*Y[1]+X[2]*Y[2]+X[3]*Y[3](在这种情况下,我们将得到-4) 很明显,X可以用二进制数字表示,例如L=32的整数类型int32。然后,我们要做的就是找到这个整数与3
L=4;X[L]={1,0,0,1};Y[L]={-4,2,1,0}
我们必须找到z=X[0]*Y[0]+X[1]*Y[1]+X[2]*Y[2]+X[3]*Y[3]
(在这种情况下,我们将得到-4
)
很明显,X可以用二进制数字表示,例如L=32的整数类型int32。然后,我们要做的就是找到这个整数与32个整数数组的点积。您有什么想法或建议如何快速完成吗?这确实需要分析,但您可能需要考虑另一种选择:
int result=0;
int mask=1;
for ( int i = 0; i < L; i++ ){
if ( X & mask ){
result+=Y[i];
}
mask <<= 1;
}
int结果=0;
int-mask=1;
对于(int i=0;i
研究SSE2的建议有用吗?它已经有了点积类型的操作,加上您可以简单地并行执行4次(或者8次,我忘了寄存器大小)简单的朴素循环迭代。
SSE也有一些简单的逻辑类型操作,因此它可以在不使用任何条件操作的情况下进行加法而不是乘法。。。同样,您必须查看哪些操作可用。您可以将位向量存储为一个整数序列,其中每个整数将一对系数打包为位。然后,分量乘法等价于位和。这样,您只需计算设置位的数量,可以这样做:
inline int count(uint32_t x) {
// see link
}
int dot(uint32_t a, uint32_t b) {
return count(a & b);
}
有关计数设定位的bit hack,请参阅
编辑:抱歉,我刚刚意识到只有一个向量包含{0,1}元素,而另一个不包含。这个答案只适用于两个向量都被限制为{0,1}集合中的系数的情况。如果它是1,那么你希望所有比特都通过,如果它是0,那么就没有比特通过。因此,您希望以某种方式将1转换为-1(即0xFFFFFF),并且0保持不变。那只是-X。。。。所以你
Y & (-X)
对于每个元素。。。工作完成了吗
Edit2:要给出一个代码示例,您可以这样做并避免分支:
int result=0;
for ( int i = 0; i < L; i++ )
{
result+=Y[i] & -(int)((X >> i) & 1);
}
int结果=0;
对于(int i=0;i>i)和1);
}
当然,最好将1和0保持在整数数组中,从而避免移位
编辑:同样值得注意的是,如果Y中的值的大小为16位,那么每个操作可以执行其中2个和操作(如果您有64位寄存器,则为4个)。不过,这确实意味着将X值1乘以1求反为一个更大的整数
ie YVals=-4,16位中的3=0xFFFC,0x3。。。输入1 32位,得到0xFFFC0003。如果将1,0作为X VAL,那么将0xFFFF0000和2组成一个位掩码,得到2个结果,即1按位and运算
另一编辑:
如果你想知道如何使用第二种方法的代码,像这样的代码应该可以工作(尽管它利用了未指定的行为,所以它可能不适用于每一个编译器..适用于我遇到的每一个编译器)
union int1632
{
int32_t i32;
int16_t i16[2];
};
int结果=0;
对于(int i=0;i<(L&~0x1);i+=2)
{
INT3264Y3264;
y3264.i16[0]=Y[i+0];
y3264.i16[1]=Y[i+1];
INT3264X3264;
x326.i16[0]=-(int16_t)((X>>(i+0))&1);
x326.i16[1]=-(int16_t)((X>>(i+1))&1);
int3264 res3264;
res3264.i32=y3264.i32&x3264.i32;
结果+=res3264.i16[0]+res3264.i16[1];
}
如果(i>i)和1);
希望编译器能优化赋值(我不确定,但我的想法可以重新设计,这样它们肯定会优化),并给你一个小的加速,因为你现在只需要按1位进行运算,而不是按2位进行运算。速度会很小,但…此解决方案与Micheal Aaron的相同,但比Micheal Aaron的略快(根据我的测试):
long Lev=1;
long Result=0
for (int i=0;i<L;i++) {
if (X & Lev)
Result+=Y[i];
Lev*=2;
}
long Lev=1;
长结果=0
对于(inti=0;iRepresenteX
,使用X[i]=1的位置的链接列表。
要找到所需金额,您需要O(N)
操作,其中N
是列表的大小。这个问题可能没有一般的答案。您需要在所有不同的目标下分析代码。性能将取决于编译器优化,例如大多数现代CPU上可用的循环展开和SIMD指令(x86、PPC和ARM都有自己的实现)。试试这个:
int result=0;
for ( int i = 0; i < L; i++ ){
result+=Y[i] & (~(((X>>i)&1)-1));
}
int结果=0;
对于(int i=0;i>i)&1)-1);
}
这避免了条件语句,并使用逐位运算符将标量值屏蔽为0或1。我看到过许多采用位欺骗(以避免分支)的响应,但没有一个正确的循环:/
优化@Goz
答案:
int result=0;
for (int i = 0, x = X; x > 0; ++i, x>>= 1 )
{
result += Y[i] & -(int)(x & 1);
}
优点:
- 无需每次执行
i
位移位操作(X>>i
)
- 如果
X
的高位包含0,则循环会更快停止
现在,我想知道它是否运行得更快,特别是因为for循环的过早停止可能不像编译时常量那样容易展开循环;
对于(int i=0;i
由于大小并不重要,我认为以下可能是最有效的通用代码:
int result = 0;
for (size_t i = 0; i < 32; ++i)
result += Y[i] & -X[i];
int结果=0;
对于(尺寸i=0;i<32;++i)
结果+=Y[i]&-X[i];
位编码X
不会给表带来任何东西(即使循环可能会像@Mathieu正确指出的那样提前终止)。但是在循环中省略if
会带来什么
当然,正如其他人所指出的,循环展开可以大大加快速度
int result=0;
for (int i = 0, x = X; x > 0; ++i, x>>= 1 )
{
result += Y[i] & -(int)(x & 1);
}
result = 0;
for(int i = 0; i < L ; i++)
if(X[i]!=0)
result += Y[i];
int result = 0;
for (size_t i = 0; i < 32; ++i)
result += Y[i] & -X[i];
int dot8(unsigned int X, const int Y[])
{
switch (X)
{
case 0: return 0;
case 1: return Y[0];
case 2: return Y[1];
case 3: return Y[0]+Y[1];
// ...
case 255: return Y[0]+Y[1]+Y[2]+Y[3]+Y[4]+Y[5]+Y[6]+Y[7];
}
assert(0 && "X too big");
}
int dot32(unsigned int X, const int Y[])
{
return dot8(X >> 0 & 255, Y + 0) +
dot8(X >> 8 & 255, Y + 8) +
dot8(X >> 16 & 255, Y + 16) +
dot8(X >> 24 & 255, Y + 24);
}
static int dot4(unsigned int X, const int Y[])
{
switch (X)
{
case 0: return 0;
case 1: return Y[0];
case 2: return Y[1];
case 3: return Y[0]+Y[1];
//...
case 15: return Y[0]+Y[1]+Y[2]+Y[3];
}
}
int dot(unsigned int X, const int Y[])
{
return (Y[0] & -!!(X & 1<<0)) +
(Y[1] & -!!(X & 1<<1)) +
(Y[2] & -!!(X & 1<<2)) +
(Y[3] & -!!(X & 1<<3)) +
//...
(Y[31] & -!!(X & 1<<31));
}
int result=0;
for ( int x=X; x!=0; x>>=4 ){
switch (x&15) {
case 0: break;
case 1: result+=Y[0]; break;
case 2: result+=Y[1]; break;
case 3: result+=Y[0]+Y[1]; break;
case 4: result+=Y[2]; break;
case 5: result+=Y[0]+Y[2]; break;
case 6: result+=Y[1]+Y[2]; break;
case 7: result+=Y[0]+Y[1]+Y[2]; break;
case 8: result+=Y[3]; break;
case 9: result+=Y[0]+Y[3]; break;
case 10: result+=Y[1]+Y[3]; break;
case 11: result+=Y[0]+Y[1]+Y[3]; break;
case 12: result+=Y[2]+Y[3]; break;
case 13: result+=Y[0]+Y[2]+Y[3]; break;
case 14: result+=Y[1]+Y[2]+Y[3]; break;
case 15: result+=Y[0]+Y[1]+Y[2]+Y[3]; break;
}
Y+=4;
}
template<int I> inline void calcZ(int (&X)[L], int(&Y)[L], int &Z) {
Z += X[I] * Y[I]; // Essentially free, as it operates in parallel with loads.
calcZ<I-1>(X,Y,Z);
}
template< > inline void calcZ<0>(int (&X)[L], int(&Y)[L], int &Z) {
Z += X[0] * Y[0];
}
inline int calcZ(int (&X)[L], int(&Y)[L]) {
int Z = 0;
calcZ<L-1>(X,Y,Z);
return Z;
}