C++ C/C++;优化:快速否定双打

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

我需要快速地否定大量的双打。如果位\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.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