常数优化

Posted oierwa-luogu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常数优化相关的知识,希望对你有一定的参考价值。

虽然大部分的题都不会卡常数,但是万一卡常还是很恶心的,让我们一起来看看常数优化吧~   (蒟蒻太蒟了,可能无法覆盖到所有常数优化,请见谅~)


register&inline

CPU有高速缓存,那个速度非常快,但占用内存小,加上这register后,这个变量的存放位置就在register高速缓存里。一般用于频繁修改的变量(如循环中的变量)

如:

for(register int i++;i<=100;++i) cout<<i<<" ";

在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

其实,inline在内部的工作就是在每个for循环的内部任何调用inline函数的地方都换成了函数内部的内容

inline工作方式的影响,inline 的定义是有限制的,比如,内部有复杂语句如循环语句,或者是递归函数都不适合用inline 。

那万一不小心把递归函数调用成了inline,岂不就炸了,那为什么有时不会炸呢?

因为inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。

这里有一篇对inline的解释,写得很详细,本文以上内容摘抄了一部分此博文内容

变量的速度

在C++的运算中,bool,char都要转int运算,而bool是比char要慢的(恐怖)。

而且int是最快的运算类型。

所以说,让我们来看一看各个变量的运行速度。

先贴测试用的代码:

 1 #include <bits/stdc++.h>
 2 #include <windows.h>
 3 using namespace std;
 4 int main(){
 5     bool boolnum=0;
 6     char charnum=100;
 7     int integer=0;
 8     short shortnum=1;
 9     unsigned uninteger=2;
10     SYSTEMTIME sys_time;
11     GetLocalTime( &sys_time );
12     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
13     for(register int i=1;i<=1000000000;++i) boolnum=-boolnum+integer;//1
14     
15     GetLocalTime( &sys_time );
16     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
17     for(register int i=1;i<=1000000000;++i) charnum=charnum+shortnum-1;
18     
19     GetLocalTime( &sys_time );
20     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
21     for(register int i=1;i<=1000000000;++i) integer=-integer+shortnum+5;
22     
23     GetLocalTime( &sys_time );
24     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
25     for(register int i=1;i<=1000000000;++i) shortnum=-shortnum+6;
26     
27     GetLocalTime( &sys_time );
28     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
29     for(register int i=1;i<=1000000000;++i) uninteger=uninteger+6-shortnum;//5
30     
31     unsigned short unshortint=12;
32     long long longinteger=100;
33     unsigned long long unlongint=6;
34     float fnum=0.0299;
35     double doublefnum=0.001445;
36     long double lldnum=0.0008743;
37     
38     GetLocalTime( &sys_time );
39     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
40     for(register int i=1;i<=1000000000;++i) unshortint=boolnum+integer-unshortint;
41     
42     GetLocalTime( &sys_time );
43     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
44     for(register int i=1;i<=1000000000;++i) longinteger=-longinteger+integer-1;
45     
46     GetLocalTime( &sys_time );
47     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
48     for(register int i=1;i<=1000000000;++i) unlongint=unlongint+longinteger;//8
49     
50     GetLocalTime( &sys_time );
51     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
52     for(register int i=1;i<=1000000000;++i) fnum=-fnum+lldnum;
53     
54     GetLocalTime( &sys_time );
55     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
56     for(register int i=1;i<=1000000000;++i) doublefnum=-fnum+doublefnum+lldnum;
57     
58     GetLocalTime( &sys_time );
59     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
60     for(register int i=1;i<=1000000000;++i) lldnum=-lldnum+doublefnum+0.001;
61     
62     GetLocalTime( &sys_time );
63     printf( "%02d:%02d.%03d
",sys_time.wMinute, sys_time.wSecond,sys_time.wMilliseconds);
64 }

很恐怖,有木有?我们来看看运行结果:

 1 29:26.013
 2 29:28.336
 3 29:30.634
 4 29:32.951
 5 29:35.079
 6 29:37.373
 7 29:39.112
 8 29:41.693
 9 29:44.027
10 29:47.182
11 29:50.909
12 29:55.212

用第(i-1)行的数减第i行的数,分别指的是:

bool,char,int,short,unsigned int,unsigned short,long long,unsigned long long,float,double,long double

的运行速度。

所以,运行2,000,000,000(二十亿)次的速度:

2.323s;
2.298s;    //char在程序中的运行难度要高于bool但仍比bool快!
2.317s;    //但我还不太明白为什么int会有点慢
2.218s;    //short和int差不多
2.294s;    //unsigned加上运算速度差不多
1.739s;    //为什么unsigned short 会这么快?
2.581s;    //long long 确实要略慢一些
2.334s;
3.155s;    //果然,float变慢了许多。
3.727s;    //double 与 float 差不多
4.303s.    // long double 的确是慢。

自增

在C++循环语句中,我们经常见到这样的语句:

for(int i=1;i<=n;i++)

但是i++,++i 除了调用值不同外,还有什么不同呢?

++i比i++快!   (恐怖吧)

所以我打的循环总是这样的:

for(register int i=1;i<=n;++i)

它的速度比前者快3倍以上

运算符

我们假设位运算的时间复杂度为O(1),

那么加法时间复杂度为O(5~10) 减法略高;乘法时间复杂度O(10~20),除法时间复杂度O(20~30)而%运算时间复杂度高达O(100多)!而且,mod的数越大,就越慢!!!(所以说如果一道卡常的hash,最好还是常优一下)

所以我们可以各种恶心:

1 乘二方&除以二方各种左移右移
2 乘二后按位与就是*2+1
3 %2直接用&1

还有

if()else语句比()?():()语句要慢;逗号运算符比分号运算符要快

还有MOD运算直接  a%b 用 a=a-(int)(a/b)*b; 代替,这样可以快一倍以上,但这种优化可能会挂。

 

结构体访问

结构体访问这东西有点神奇~

比如你:

开数组:a[10001],b[10001],n[10001];
调用时连续调用a[i],b[i],c[i]慢的一批。
开一个结构体:
struct rec{
    int a,b,n;
}r[10001];
调用时r[i].a,r[i].b,r[i].n要快一些

读入/输出优化

读入,输出优化,这个也有些恶心。一般大家直接写个read()/write()函数吧,里面用getchar()和putchar();

温馨提醒一下:一般这样就够了,但fread和fwrite会更快~

数组压维

在C++中即使你开了多维数组,在内存中仍然是线性存储的。

所以你用二维数组调用时,编译器找到这个位置要很久,不断调用更恶心!!!

但是一位数组调用会简单一些,所以说当你开一位数组时,效率会高一些。

所以你可以:

在调用a[i][j]时,用a[i*j]调用。
定义int a[m+1][n+1]时,用int a[m*n+1]定义。
甚至这样还更省空间一些!!!

循环展开

循环展开,这东西在DFS中很有用。

首先举个输入时的栗子:

1 for(register int i=1;i<=n;i+=5) cin>>a[i]>>a[i+1]>>a[i+2]>>a[i+3]>>a[i+4]>>a[i+5];
2 这样会快很多~

在矩阵中DFS时,经常这么写:

int px[5]={0,1,0,-1,0};
int py[5]={0,0,-1,0,1};
void search(...){
    for(register int i=1;i<=4;++i){
        ......
    }
}

殊不知,这样很慢,既费空间,又费时间。
所以可以这么打:

void search(int nx,int ny,...){
    ......
    ......
    search(nx+1,ny);
    search(nx-1,ny);
    search(nx,ny+1);
    search(nx,ny-1);
}

DFS时的技巧

循环展开上面提过了。

所以DFS通常有两种:剪枝和判定前移

一棵完整的搜索树是非常累赘的,通常有许多许多节点。而且每层节点都是指数级别的增加,如果能在尽可能早的地方去掉这些无用分支,搜索的性能就会有大幅度的提升。

剪枝,这种方法极常见,常见的剪枝有:

最优化剪枝
可行性剪枝
运用数学技巧的剪枝
根据题目性质的剪枝
底层优化
面向数据剪枝

最优化剪枝指当前分支算出的答案是绝对的最优答案,就不用搜索了。

可行性剪枝指当前分支的情况无论如何选择最优答案,都不会再更新答案,可以不用继续搜索了

面向数据剪枝是最有效的剪枝(比如打表......)

所谓的判定前移,其实只是底层优化的一种,就是将结束搜索的判定从下一层挪到这一层来,可以大幅减少时间消耗。

常数优化这种东西是非常多的,一般掌握几种就好了。



以上是关于常数优化的主要内容,如果未能解决你的问题,请参考以下文章

禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?

在 Python 中优化微分方程中的常数

优化 C# 代码片段、ObservableCollection 和 AddRange

常数优化

使用 C++ 反转句子中的每个单词需要对我的代码片段进行代码优化

如何优化C ++代码的以下片段 - 卷中的零交叉