当我们遇到这样情况的时候,是不是不知所措呢?怎么防止遇到这样的情况呢,我们来分析一下程序加速的一些方法。 : M9 O+ q/ [( G5 G6 j 硬件、编译器造成的% S- y, w f0 ~$ l7 X$ i! I 使用好点的电脑无疑是一个操作上的最佳选择,其次,对于编译器也是可以编译选项优化的,例如在VS环境中,可以通过配置属性来实现,具体步骤如下,大家可以参考:http://blog.csdn.net/yizhou2010/article/details/52635288 代码编写风格3 E) i. i( C# u7 e多使用自加、自减指令和复合赋值表达式: [2 @% t I8 F' ^; j# t/ l* p- E " Q7 U! V. L' Y6 G$ V2 A 9 u0 q8 t. D: d$ K3 n 你觉得使用i++ ,i = i + 1,i += 1有区别吗?我们来测试一下C代码: void asd() {}( F" f7 L6 m& @3 u. B, w Uint main() { int i=0; i++; asd(); //方便区分上下文8 ^ Y# x( t. N9 ?6 \% y i=i+1; asd(); i+=1; return 0; }& K+ Q) R1 J2 N: R& r; s ) Z- L# K8 B( n* C 反汇编: 3 _' \( ?" ~! I* Y mov [rbp+i], 0 //i的初始化add [rbp+i], 1 //i++; call _Z3asdv ; asd(void) B' |0 I5 g0 a# c- h add [rbp+i], 1 //i=i+1; call _Z3asdv ; asd(void)- C/ E0 q0 Z) P add [rbp+i], 1 //i+=1; 我们看到这个结果是一样的,但是在更加复杂的表达式中就会多生成几个指令了,而且用 i += 1的,总是比写 i = i + 1的要稍微那么好看些。 * M- ?5 F3 c" A9 b! n3 G 除法就是由乘法的过程逆推来的,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量,一般来说,更耗时间一些,用一个demo来测试一下 auto time_start = std::chrono::system_clock::now();3 n1 U/ k: p1 @5 d7 R7 A7 N. B fint iCount = 100000; double k ;( E+ L6 |8 h) `' {2 W for (int i = 0; i < 1000000; i++) {( F3 o: y! X3 e- J4 t+ \$ ?2 q' E tmp = iCount / 2;7 P) a* B9 E' M! F( a0 N: v } std::chrono::duration<double> time_spend = std::chrono::system_clock::now() - time_start; double test1 = time_spend.count() * 1000; cout<<"test1 cost "<<time_cost<<" ms"<<endl;$ q0 s4 m: d; }' L: G % @1 t7 `7 M, v5 v. f1 x- K time_start = std::chrono::system_clock::now() ;; d, D" H0 ~5 o& l' V+ K for (int i = 0; i < 1000000; i++) {1 f* b7 e3 T$ U- _. t tmp = iCount * 0.5f;3 G% W" P3 k1 u0 b) j' [ } time_spend = std::chrono::system_clock::now() - time_start; test2 = time_spend.count() * 1000; cout<<"test2 cost "<<time_cost<<" ms"<<endl;5 ~. ^1 K9 M8 W0 r' J7 O 8 Z. m; d/ ~ ~5 ~* G6 w time_start = std::chrono::system_clock::now() ;: w, X [3 d' v* B+ o for (int i = 0; i < 1000000; i++)6 J- T- H( @# U {" }: A( a/ F" r/ t* t* _ tmp = iCount >>1;1 Q. l' Q$ k) d5 y0 g4 x6 Q }7 B9 i2 q# ? _" V time_spend = std::chrono::system_clock::now() - time_start; test3 = time_spend.count() * 1000;$ d; @% i( F. N" f/ i cout<<"test3 cost "<<time_cost<<" ms"<<endl;- R5 J% @, q% p 我们输出结果会发现,移位和乘法比除法要省3-5倍时间,移位相对而言是最省时间的。 u! }( X. w2 E: o% l6 L: S 多用直接初始化,少用拷贝初始化string s1 = "hiya"; // 拷贝初始化 - g! D+ d; q3 [3 ` string s2("hello"); // 直接初始化1 {- t# w* S4 f" x# l( X string s3(10, 'c'); // 直接初始化 当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换,会浪费一定的资源时间,而直接初始化是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数和拷贝构造函数。 我们来看看Primer中怎么说的
还有一段说到: 局部变量、静态局部变量、全局变量与静态全局变量 4 e- C$ A' n& n3 v- W3 C8 R
静态变量是低效的,当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中 避免大的循环,循环中避免判断语句* D! U' v% G% b; K% k 在写程序过程中,最影响代码运行速度的往往都是循环语句,我记得当时在写matlab的时候,处理大数据,都是禁止用循环的,特别是多层嵌套的循环语句。 其次,尽量将循环嵌套控制在 3 层以内,有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大地降低。同时,这样程序的执行效率也会很低。因此,如果代码循环嵌套超过 3 层,建议重新设计循环或将循环内的代码改写成一个子函数。 for (i=0;i<100;i++){, O" s6 { K% v for (j=0;j<5;j++)9 Q/ {/ o' w: v6 d( `+ q { for (j=0;j<5;j++) { /*处理代码*/% C; M" f3 U8 s8 B Z3 o6 t0 X }, q) r! J8 k& P* u: H }+ n- m b$ y* G0 v }1 o6 l( n% U* J6 i 多重 for 循环中,如果有可能,应当尽量将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数 for (i=0;i<100;i++)% E- Y% g/ h6 p O" S$ h6 [{ for (j=0;j<5;j++); j# _$ t& E# ^- |1 p3 o5 `* q { /*处理代码*/1 G, J1 T' h2 }8 C R } } + S5 Y$ `2 y! N a 改为: for (j=0;j<5;j++){ for (i=0;i<100;i++)7 M8 A: D& e: L" u' a { /*处理代码*/ } } 逻辑判断不要在循环中使用,当 for 循环的次数很大时,执行多余的判断不仅会消耗系统的资源,而且会打断循环“流水线”作业,使得编译器不能对循环进行优化处理,降低程序的执行效率 if (condition)7 Q) j' Z& a& {1 d+ h9 H{ for (i = 0;i < n;i++) { /*处理代码*/ } D1 t* Q# s/ [: C# s5 N }$ |& C+ q6 J2 ] else( x. K8 o( K* D; _' p7 h {( g( p; d8 l$ {; p0 j# R for (i = 0;i < n;i++): f$ z, A9 N0 H {% u1 s' t& c, G* C1 ?4 i /*处理代码*/$ B2 ^# d* Y8 d; J" Y6 q }3 X$ l) ^! u$ Y8 Q( R0 e- ?+ U } 尽量避免递归,递归就是不停的调用自身,所以非常消耗资源,甚至造成堆栈溢出和程序崩溃等等问题! int Func(int n){" b, g3 c, M6 ^3 ]8 B2 | if(n < 2)( ~8 U5 H2 X1 M) c% a/ B. p return 1; else e! g/ w4 R* g X+ F& V) O! r% C return n*Func(n-1);7 `1 }8 ?# w2 F+ ?3 e/ q }7 a/ I' W- y6 d' q$ z5 a* M3 U 因此,掌握循环优化的各种实用技术是提高程序效率的利器,也是一个高水平程序必须具备的基本功。 c" N* O( \- l* X7 e 尽量不使用继承和多重继承1 R$ t( k3 i- f" |7 x) V0 e+ P多重继承增加了类的继承层次的复杂性,调试难度增加当然风险也增加了,而且使用父类指针指向子类对象变成了一件复杂的事情,得用到C++中提供的dynamic_cast来执行强制转换。但是dynamic_cast是在运行期间而非编译期间进行转换的,因此会会带来一些轻微的性能损失,建议类型转换尽量采用c++内置的类型转换函数,而不要强行转换 . Y5 m/ X' D+ i 少用模板,因为模板是编译期技术,大量采用模板也会增加编译时间5 m7 z& Q2 n2 G/ J# ~' y. h6 J' b: p2 c" L, L7 | 在c++primer3中,有一句话: 编码依赖性 声明与实现分离,删除不必要的#include 4 [- X0 ^, n# X8 `
bool func1(string s1, string s2) ! m, U5 R6 L# g# [ bool func2(string *s1, string *s2) bool func3(string &s1, string &s2)* {: O. ?' P( q# K$ }, q 指针和引用都不会创建新的对象,函数func2和func3不需要调用析构和构造函数,函数func1使用值传递在参数传递和函数返回时,需要调用string的构造函数和析构函数两次。 + f* J" o4 l# ^+ O 适当的采用PIMPL模式很实用的一种基础模式,通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。将实现放到CPP里,主要作用在于编译分离,其实是增加了编码量以及初次编译时长,增量编译才体现作用。例如:指针的大小为(64位)或32(8位),X发生变化,指针大小却不会改变,文件c.h也不需要重编译。 , j- d. P4 S# `9 u4 ^% [2 } 方法还有很多,比如使用多线程,多任务并行编译,分布式编译,预编译等等,另外,在编译大型项目时,分布式编译更优,往往能够大幅度提升性能。 |
| 谢谢分享 |
微信公众号
手机版