常数优化
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,并转换?
优化 C# 代码片段、ObservableCollection 和 AddRange