免责声明
本文仅供参考,由读者引发的一切后果,任何责任由读者自行承担。后果包括但不限于:
1. 优化后程序变慢。
2. 某些 O J / O I OJ/OI OJ/OI上导致的编译错误。
3. 文章中的某些错误引起的争论。
一、 G C C GCC GCC系列
关于GCC的黑科技想必大家也就知道这几句吧:
#pragma GCC optimize (3)
#pragma GCC optimize (2)
#pragma GCC optimize (1)
俗称“吸氧”。
在这里小编就简单地讲一下 O 1 O1 O1, O 2 O2 O2, O 3 O3 O3, O s Os Os(怎么还有个 O s Os Os)
1. O 1.O 1.O系列
( 1 ) (1) (1). O 1 O1 O1
包含下列选项
-fauto-inc-dec
-fcprop-registers
-fdce
-fdefer-pop
-fdelayed-branch
-fdse
-fguess-branch-probability
-fif-conversion2
-fif-conversion
-finline-small-functions
-fipa-pure-const
-fipa-reference
-fmerge-constants -fsplit-wide-types
-ftree-builtin-call-dce
-ftree-ccp
-ftree-ch
-ftree-copyrename
-ftree-dce
-ftree-dominator-opts
-ftree-dse
-ftree-fre
-ftree-sra
-ftree-ter
-funit-at-a-time
-fomit-frame-pointer
( 2 ) (2) (2). O 2 O2 O2
除了加载-O1的选项外,还加载
-fthread-jumps
-falign-functions -falign-jumps
-falign-loops -falign-labels
-fcaller-saves
-fcrossjumping
-fcse-follow-jumps -fcse-skip-blocks
-fdelete-null-pointer-checks
-fexpensive-optimizations
-fgcse -fgcse-lm
-findirect-inlining
-foptimize-sibling-calls
-fpeephole2
-fregmove
-freorder-blocks -freorder-functions
-frerun-cse-after-loop
-fsched-interblock -fsched-spec
-fschedule-insns -fschedule-insns2
-fstrict-aliasing -fstrict-overflow
-ftree-switch-conversion
-ftree-pre
-ftree-vrp
( 3 ) (3) (3). O 3 O3 O3
除了加载-O2外,还加载
-finline-functions
-funswitch-loops’?
-fpredictive-commoning
-fgcse-after-reload
-ftree-vectorize
( 4 ) (4) (4). O s Os Os
在研究编译驱动的makefile的时候,发现GCC的命令行里面有一个-Os的优化选项。
遍查GCC文档,发现了-O0,-O1,-O2,-O3,就是没有发现-Os。
祭出GOOGLE,搜了一下,终于发现这篇文章说明了-Os的作用:
http://www.linuxjournal.com/article/7269
原来-Os相当于-O2.5。是使用了所有-O2的优化选项,但又不缩减代码尺寸的方法。
除了包含-O2的开关外,-Os还会使得下列开关禁用。
-falign-functions
-falign-jumps
-falign-loops
-falign-labels
-freorder-blocks
-freorder-blocks-and-partition
-fprefetch-loop-arrays
-ftree-vect-loop-version
另外,对于多个-O选项的情形,最后一个加载的为有效。比如gcc –O1 –Os –O3 –o test test.c,有效的优化开关为-O3。
一般来说,用的最多的是-O3和-Os,如果遇到程序运行不正常的问题,请降低优化级别,如把-O3改为-O2(情况很少见)。
2. 2. 2.针对目标机器
( 1 ) (1) (1)-march=cpu-type
为cpu-type所针对的机器开启需要的指令集。
cpu-type可以为pentium4、core2、athlon-4等(具体参见文档),比如-march=core2
时,则会开启core2所支持的MMX、SSE、SSE2、SSE3、SSSE3
指令集。
另外还支持native
类型,为编译器所在目前的CPU类型优化指令集,指定-march=native
。
( 2 ) (2) (2) -mfpmath=unit
选择浮点运算单元。
unit
可以为387和sse
。
387为x86系列的默认值,使用标准的387浮点协处理器。
sse
为x64的默认值,使用sse
指令集。
一般你的程序如果有大量的浮点运算的话,在P4和K8以上级别的处理器上推荐开启-mfpmath=sse
。
( 3 ) (3) (3)加载指定指令集。
可以使用-msse2或-msse4.1加载指定的指令集。
3. 3. 3.其他比较有效的选项
( 1 ) (1) (1)-ftracer
执行尾部复制以扩大超级块的尺寸,它简化了函数控制流,从而允许其它的优化措施做的更好。单独使用没啥意义,和其他优化选项一起使用很有效。
( 2 ) (2) (2)-ffast-math
违反IEEE/ANSI标准以提高浮点数计算速度,是个危险的选项,仅在编译不需要严格遵守IEEE规范且浮点计算密集的程序考虑采用。不考虑精度时使用这个选项速度会加快。
( 3 ) (3) (3)-fivopts
在trees上执行归纳变量优化。
( 4 ) (4) (4)-ftree-parallelize-loops=n
使循环并行化。只当循环无数据依赖时使用,在多核CPU上时使用才会有利。
( 5 ) (5) (5)-ftree-loop-linear
在trees上进行线型循环转换。它能够改进缓冲性能并且允许进行更进一步的循环优化。
( 6 ) (6) (6)-fforce-addr
必须将地址复制到寄存器中才能对他们进行运算。由于所需地址通常在前面已经加载到寄存器中了,所以这个选项可以改进代码。
( 7 ) (7) (7)-floop-interchange
交换循环变量。
例如:
DO J = 1, M
DO I = 1, N
A(J, I) = A(J, I) * C
ENDDO
ENDDO
会改变为
DO I = 1, N
DO J = 1, M
A(J, I) = A(J, I) * C
ENDDO
ENDDO
改变后,如果N比缓冲区大的话,会更有效率。这是因为Fortran里数组是以列主元为排列方式的。当然这个选项并不仅仅用于Fortran,Gcc家族的编译器都有效。
( 8 ) (8) (8)-fvisibility=hidden
设置默认的ELF镜像中符号的可见性为隐藏。使用这个特性可以非常充分的提高连接和加载共享库的性能,生成更加优化的代码,提供近乎完美的API输出和防止符号碰撞。我们强烈建议你在编译任何共享库(Dll)的时候使用该选项。
-fvisibility-inlines-hidden
默认隐藏所有内联函数,从而减小导出符号表的大小,既能缩减文件的大小,还能提高运行性能,强烈建议你在编译任何共享库的时候使用该选项。
( 9 ) (9) (9)-minline-all-stringops
默认时GCC只将确定目的地会被对齐在至少4字节边界的字符串操作内联进程序代码。该选项启用更多的内联并且增加二进制文件的体积,但是可以提升依赖于高速 memcpy
, strlen
, memset
操作的程序的性能。
( 10 ) (10) (10)-m64
生成专门运行于 64 64 64位环境的代码,不能运行于 32 32 32位环境,仅用于 x 86 _ 64 x86\_64 x86_64[含 E M T 64 EMT64 EMT64]环境。
( 11 ) (11) (11)-fprefetch-loop-arrays
生成数组预读取指令,对于使用巨大数组的程序可以加快代码执行速度,适合数据库相关的大型软件等。具体效果如何取决于代码。不能和-Os一起使用。
( 12 ) (12) (12)-pipe
在编译过程的不同阶段之间使用管道而非临时文件进行通信,可以加快编译速度。建议使用。
4. 4. 4.推荐选项开关
综上,比较安全的开关为
-pipe -O3(-Os)
-march=native
-mfpmath=sse
-msse2 -ftracer
-fivopts
-ftree-loop-linear
-fforce-addr
如果不需要多高的精度,比如GUI框架之类,加入
-ffast-math
如果是编译的是共享库(. d l l dll dll,. a a a)加入
-fvisibility=hidden
-fvisibility-inlines-hidden
注意某些比赛不能使用 O 1 , O 2 , O 3 , O s O1,O2,O3,Os O1,O2,O3,Os
二、底层优化(坑)
1. I / O I/O I/O优化
I / O I/O I/O优化是卡常中最常用的技巧,当数据较大的时候,读入输出占用了很多时间。
( 1 ) (1) (1)读入优化
流输入方式很方便,不需要记忆占位符,但每次读入时,它都要检测是否和stdin
的同步(是否被freopen
改变/是否被scanf
读取),因此它是可以和scanf
混用的。但也导致了它每次都要从数据开始位置跳转到当前读入的位置,浪费了大量时间,可以用std::ios::sync_with_stdio(false)
关闭两者的同步以加快速度,这样做之后会比scanf
还快!但必须注意,调用后不能再用freopen
,但是还可以用fstream
当然,更好的方法是用getchar
自己写读入函数。
inline void read(int &sum)
{
char ch=getchar();
int tf=0;
sum=0;
while((ch<'0'||ch>'9')&&(ch!='-'))
ch=getchar();
tf=((ch=='-')&&(ch=getchar()));
while(ch>='0'&&ch<='9')
sum=sum*10+(ch-48),ch=getchar();
(tf)&&(sum=-sum);
}
这样效率有了很大的提升,而且可以和scanf
混用(字符串等),第7行和第10行的代码后面会说。
我们知道,getchar
是逐字符读取的,在stdio.h
中,有一个fread
函数,能整段读取,比getchar
还快,并且支持freopen
(完美兼容)和fopen
(需要把下面的所有stdin
改成你的文件指针)
函数原型:size_t fread(void *buffer,size_t size,size_t count,FILE *stream);
作用:从stream
中读取count
个大小为size
个字节的数据,放到数组buffer
中,返回成功了多少个大小为size个字节的数据。
inline char nc()
{
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
//#define nc getchar
inline void read(int &sum)
{
char ch=nc();
int tf=0;
sum=0;
while((ch<'0'||ch>'9')&&(ch!='-'))
ch=nc();
tf=((ch=='-')