C++ c++;与可读性更强的方法相比

C++ c++;与可读性更强的方法相比,c++,performance,bitwise-operators,C++,Performance,Bitwise Operators,我最近正在为一个研究项目编写一些代码,在这个项目中效率非常重要。我一直在考虑删除一些常规方法,改用按位异或。我想知道的是,如果有不同(如果我执行这个操作数百万次),或者在g++中使用03之后,它是否会相同,这是否会产生影响 我想到的两个例子是: 我有一个例子(我正在处理纯正整数),如果n是奇数,我需要将n改为n-1,如果n是偶数,我需要将n改为(n+1)。我想我有几个选择: if(n%2) // or (n%2==0) and flip the order n=n-1 else

我最近正在为一个研究项目编写一些代码,在这个项目中效率非常重要。我一直在考虑删除一些常规方法,改用按位异或。我想知道的是,如果有不同(如果我执行这个操作数百万次),或者在g++中使用03之后,它是否会相同,这是否会产生影响

我想到的两个例子是:

我有一个例子(我正在处理纯正整数),如果n是奇数,我需要将n改为n-1,如果n是偶数,我需要将n改为(n+1)。我想我有几个选择:

if(n%2) // or (n%2==0) and flip the order
    n=n-1
else
    n=n+1

最后:

n=n^1;
所有的方法显然都是一样的,但我觉得第三种方法是最有效的

下一个例子是更一般的说明。假设我比较两个正整数,其中一个会比其他的好吗。或者,即使我执行此操作数百万次,差异是否真的不明显:

if(n_1==n_2)
if(! (n_1 ^ n_2) )
if( n_1 ^ n_2) else \do work here
编译器会在所有这些实例中执行相同的操作吗?我只是好奇是否有这样一个实例,我应该使用位操作,而不信任编译器为我做这项工作

修正:在正确的问题陈述中

如果n是偶数,我需要将n改为n-1;如果n是奇数,我需要将n改为(n+1)

在这种情况下,不管效率如何,
n=n^1
都是错误的

对于第二种情况,
==
将与其他任何情况一样高效(如果不是更高效的话)



一般来说,当涉及到优化时,您应该自己进行基准测试。如果一个潜在的优化不值得进行基准测试,那么它就不值得进行。

一个好的编译器将优化
n%2
,但您可以随时检查生成的程序集以查看。如果您看到divide,请自己开始优化它,因为divide的速度非常慢。

您应该信任您的编译器。gcc/++是多年开发的产物,它能够进行您可能正在考虑的任何优化。而且,如果你开始胡闹,你很可能会干扰它优化代码的努力。

检查起来很容易,只需启动反汇编程序。看一看:

f、 c:

构建和分解:

$ cc -O3 -c f.c 
$ otool -tV f.o 
f.o:
(__TEXT,__text) section
_f1:
00  pushq   %rbp
01  movq    %rsp,%rbp
04  xorl    $0x01,%edi
07  movl    %edi,%eax
09  leave
0a  ret
0b  nopl    _f1(%rax,%rax)
_f2:
10  pushq   %rbp
11  movq    %rsp,%rbp
14  leal    0xff(%rdi),%eax
17  leal    0x01(%rdi),%edx
1a  andl    $0x01,%edi
1d  cmovel  %edx,%eax
20  leave
21  ret

看起来
f1()
稍微短了一点,实际上这是否重要取决于一些基准测试。

关于确定的唯一方法是测试。我必须同意,需要一个相当聪明的编译器才能产生高效的输出:

if(n%2) // or (n%2==0) and flip the order
    n=n-1
else
    n=n+1
正如它可以为
n^=1
,但我最近还没有检查过任何类似的东西,所以不能肯定地说

至于你的第二个问题,我怀疑这有什么区别——对于这些方法中的任何一种,平等性比较都会很快结束。如果您想要提高速度,最主要的是避免涉及分支,例如:

if (a == b)
    c += d;
可以写成:
c+=d*(a==b)。看看汇编语言,第二种语言通常看起来有点凌乱(使用丑陋的粗糙度将比较结果从标志中获取到普通寄存器),但通过避免任何分支,第二种语言的性能通常会更好

编辑:至少我手头的编译器(gcc和MSVC)不会为
if
生成
cmov
,但会为
*(a==b)
生成
sete
。我将代码扩展为可测试的内容

Edit2:由于Potatoswatter提出了另一种可能性,即使用位and代替乘法,所以我决定和其他方法一起进行测试。以下是添加的代码:

#include <time.h>
#include <iostream>
#include <stdlib.h>

int addif1(int a, int b, int c, int d) { 
    if (a==b) 
        c+=d;
    return c;
}

int addif2(int a, int b, int c, int d) {
    return c += d * (a == b);
}

int addif3(int a, int b, int c, int d) {
    return c += d & -(a == b);
}

int main() {
    const int iterations = 50000;
    int x = rand();
    unsigned tot1 = 0;
    unsigned tot2 = 0;
    unsigned tot3 = 0;

    clock_t start1 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot1 +=addif1(i, j, i, x);
    }
    clock_t stop1 = clock();

    clock_t start2 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot2 +=addif2(i, j, i, x);
    }
    clock_t stop2 = clock();

    clock_t start3 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot3 +=addif3(i, j, i, x);
    }
    clock_t stop3 = clock();

    std::cout << "Ignore: " << tot1 << "\n";
    std::cout << "Ignore: " << tot2 << "\n";
    std::cout << "Ignore: " << tot3 << "\n";

    std::cout << "addif1: " << stop1-start1 << "\n";
    std::cout << "addif2: " << stop2-start2 << "\n";
    std::cout << "addif3: " << stop3-start3 << "\n";    
    return 0;
}
使用
&
而不是
*
,会带来一定的改善——几乎与
*
的情况下所带来的改善一样多。使用gcc,结果会有很大不同:

Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675
在这种情况下,使用
if
的代码比使用
*
的代码的速度要快得多,但是使用
&
的代码比任何一种都慢——慢得多!如果有人关心的话,我发现这一点非常令人惊讶,我用不同的标志重新编译了几次,用每个标志重新运行了几次,以此类推,结果是完全一致的——使用
&
的代码始终相当慢

使用gcc编译的代码的第三个版本的糟糕结果让我们回到我说的开始[并结束此编辑]:

正如我在开始时所说,“唯一确定的方法是测试”——但至少在这个有限的测试中,乘法始终优于
if
。可能存在编译器、编译器标志、CPU、数据模式、迭代计数等的一些组合,这有利于
if
而不是乘法——毫无疑问,差异小到足以使测试完全可信。尽管如此,我相信这是一种值得了解的技术;对于主流编译器和CPU来说,它似乎相当有效(尽管MSVC肯定比gcc更有用)


[恢复编辑2:]gcc使用
&
的结果表明:1)微优化可以/是特定于编译器的,2)实际结果与预期有多大不同。

n^=1
if(n%2)-n快;else++n?对我不希望编译器会优化它。由于按位操作要简洁得多,因此可能需要熟悉XOR,并在该行代码上添加注释

如果它真的对程序的功能至关重要,那么它也可以被认为是一个可移植性问题:如果你在你的编译器上测试并且速度很快,那么当你在另一个编译器上尝试时,你可能会感到惊讶。通常,这不是代数优化的问题

#include <time.h>
#include <iostream>
#include <stdlib.h>

int addif1(int a, int b, int c, int d) { 
    if (a==b) 
        c+=d;
    return c;
}

int addif2(int a, int b, int c, int d) {
    return c += d * (a == b);
}

int addif3(int a, int b, int c, int d) {
    return c += d & -(a == b);
}

int main() {
    const int iterations = 50000;
    int x = rand();
    unsigned tot1 = 0;
    unsigned tot2 = 0;
    unsigned tot3 = 0;

    clock_t start1 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot1 +=addif1(i, j, i, x);
    }
    clock_t stop1 = clock();

    clock_t start2 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot2 +=addif2(i, j, i, x);
    }
    clock_t stop2 = clock();

    clock_t start3 = clock();
    for (int i=0; i<iterations; i++) {
        for (int j=0; j<iterations; j++) 
            tot3 +=addif3(i, j, i, x);
    }
    clock_t stop3 = clock();

    std::cout << "Ignore: " << tot1 << "\n";
    std::cout << "Ignore: " << tot2 << "\n";
    std::cout << "Ignore: " << tot3 << "\n";

    std::cout << "addif1: " << stop1-start1 << "\n";
    std::cout << "addif2: " << stop2-start2 << "\n";
    std::cout << "addif3: " << stop3-start3 << "\n";    
    return 0;
}
Ignore: 2682925904
Ignore: 2682925904
Ignore: 2682925904
addif1: 4814
addif2: 3504
addif3: 3021
Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675