C++ C/C++;优化:快速否定双打
我需要快速地否定大量的双打。如果位\u生成器生成0,则必须更改符号。如果位_生成器生成1,则什么也不会发生。循环多次运行,位发生器速度极快。在我的平台上,案例2明显比案例1快。看起来我的CPU不喜欢分支。有没有更快、更便携的方法?你对案例3有什么看法C++ C/C++;优化:快速否定双打,c++,c,optimization,floating-point,C++,C,Optimization,Floating Point,我需要快速地否定大量的双打。如果位\u生成器生成0,则必须更改符号。如果位_生成器生成1,则什么也不会发生。循环多次运行,位发生器速度极快。在我的平台上,案例2明显比案例1快。看起来我的CPU不喜欢分支。有没有更快、更便携的方法?你对案例3有什么看法 // generates 0 and 1 int bit_generator(); // big vector (C++) vector<double> v; // case 1 for (size_t i=0; i<v.si
// generates 0 and 1
int bit_generator();
// big vector (C++)
vector<double> v;
// case 1
for (size_t i=0; i<v.size(); ++i)
if (bit_generator()==0)
v[i] = -v[i];
// case 2
const int sign[] = {-1, 1};
for (size_t i=0; i<v.size(); ++i)
v[i] *= sign[bit_generator()];
// case 3
const double sign[] = {-1, 1};
for (size_t i=0; i<v.size(); ++i)
v[i] *= sign[bit_generator()];
// case 4 uses C-array
double a[N];
double number_generator(); // generates doubles
double z[2]; // used as buffer
for (size_t i=0; i<N; ++i) {
z[0] = number_generator();
z[1] = -z[0];
a[i] = z[bit_generator()];
}
//生成0和1
int位_生成器();
//大向量(C++)
向量v;
//案例1
对于(size_t i=0;i如果您可以假设符号由一个特定位表示,如x86实现中,您可以简单地执行以下操作:
v[i] ^= !bit_generator() << SIGN_BIT_POSITION; // negate the output of
// bit_generator because 0 means
// negate and one means leave
// unchanged.
我会成功的
编辑:
根据评论,我应该补充一点,您可能需要做一些额外的工作来编译它,因为v
是一个double
数组,而bit\u generator()
返回int
。您可以这样做:
union int_double {
double d; // assumption: double is 64 bits wide
long long int i; // assumption: long long is 64 bits wide
};
(C的语法可能有点不同,因为您可能需要一个typedef。)
然后将v
定义为int\u double
的向量,并使用:
v[i].i ^= bit_generator() << SIGN_BIT_POSITION;
v[i].i^=bit_generator()通常,如果在循环中有一个if()
,则该循环无法矢量化或展开,并且每次循环代码必须执行一次,从而最大限度地增加循环开销。情况3应该执行得很好,特别是如果编译器可以使用SSE指令
为了好玩,如果您使用的是GCC,请使用-S-o foo.S-c foo.c
标志,而不是通常的-o foo.o-c foo.c
标志。这将为您提供汇编代码,您可以看到为您的三种情况编译的内容。您不需要查找表,一个简单的公式就足够了:
const size_t size = v.size();
for (size_t i=0; i<size; ++i)
v[i] *= 2*bit_generator() - 1;
const size\u t size=v.size();
for(size_t i=0;i除非要调整循环中向量的大小,否则请将v.size()从for表达式中取出,即
const unsigned SZ=v.size();
对于(size_t i=0;i您是否能够重写位生成器
以使其返回1和-1?这将从等式中删除一个间接过程,但可能会以一些清晰度为代价。假设实际的反运算速度很快(对于现代编译器和CPU来说,这是一个很好的假设),您可以使用在现代CPU上也很快的条件赋值,在两种可能性之间进行选择:
v[i] = bit_generator() ? v[i] : -v[i];
这避免了分支,并允许编译器对循环进行矢量化,使其更快。过早优化是平淡的SO问题的根源
在我的机器上,运行速度为5333.24 BogoMIPS,在1'000'000 double数组中对1'000次迭代的计时会产生每个表达式的以下时间:
p->d = -p->d 7.33 ns
p->MSB(d) ^= 0x80 6.94 ns
其中,MSB(d)是用于获取d
的最高有效字节的伪代码。这意味着原始d=-d
的执行时间比模糊处理的方法长5.32%。对于十亿次这样的否定,这意味着7.3秒和6.9秒之间的差异
一定有人有一大堆替身来关心优化
顺便说一句,我必须在完成时打印出数组的内容,或者我的编译器将整个测试优化为零操作码。使用迭代器而不是索引可能会获得更好的性能(但可能不会,分析它)非常依赖平台。你能指定你的吗?C中没有向量。为什么这个问题有一个C标记?@Fred:因为他可以很容易地使用一个双精度数组,答案不会改变。很抱歉C标记。v可以是一个普通数组。我需要100%的可移植解决方案。正确+1,但要放大这是一个过程或者体系结构和编译器相关的优化,OP应该在代码中清楚地标记它,可能需要适当参考IEEE 754-这适用于float
和double
,但不适用于int
。对于int
,您需要反转所有位,然后添加1作为两位的补码。@m141:这是端口如果平台使用IEEE 754浮点,则只能用于float
和double
(我不知道现在有哪个平台不使用)@m141-快速或可移植;选择其中一个。这里没有必要讨论这一点,因为任何现代编译器都已经在尽可能地使用XOR neg。在代码中显式地使用XOR neg确实不再是一个好主意(21世纪)。问题是乘法通常很慢,特别是对于双精度乘法。从正数方面来说,这是可移植的。@Mark B:我不确定双精度乘法,但整数乘以2是简单的移位(bit\u generator()
返回一个int
)@马克:如果所讨论的双精度非规范化,则不会。@马克:我不知道你所说的“双精度乘法”是什么意思。2*x-1
只是将0映射到-1和1映射到1。是的,它与案例2一样快。我还在准备更多的材料。回答不错,但使用int表示SZ是不可移植的。v.size()是无符号的,其大小足以容纳内存地址。我不知道任何平台的大小与int相同。我制作了该大小。由于“x-1>x”问题,我通常避免无符号的循环内索引。在2*f()+g();
中,f()
和g()的求值顺序
未指定。因此,当v[i+1]
需要求反时,您无法确定2*bit\u generator()+bit\u generator()
为1,当v[i]
需要求反时为2。更好的方法是val=2*bit\u generator();val+=bit\u generator()
当然。我假设这是某种随机生成器,在这种情况下,求值顺序无关紧要。这如何避免生成分支?因为SSE允许您使用掩码在一条指令中选择两个源中的一个,而不使用分支。我尝试过。这与我的平台上的案例1相同。也许我需要启用SSE?最后我尝试了cked,1'000'000'000大于100000'000'000,约为
const size_t SZ=v.size();
for (size_t i=0; i<SZ; i+=2) // manual loop unrolling
{
int val=2*bit_generator()+bit_generator();
switch(val) // only one conditional
{
case 0:
break; // nothing happes
case 1:
v[i+1]=-v[i+1];
break;
case 2:
v[i]=-v[i];
break;
case 3:
v[i]=-v[i];
v[i+1]=-v[i+1];
}
}
// not shown: wrap up the loop if SZ%2==1
v[i] = bit_generator() ? v[i] : -v[i];
p->d = -p->d 7.33 ns
p->MSB(d) ^= 0x80 6.94 ns