1、13:34:09,1,C+程序设计教程(第二版),第六章 性能 Chapter 6 Performance,清华大学出版社 钱 能,13:34:09,2,提高性能的意义:性能对提高编程能力举足轻重如何提高性能?以合理使用资源为前提,尽快完成任务的能力称为效率效率影响性能,提高效率就能提高性能学习目标:1. 扩展视野,对同一问题的不同要求,模仿各种编程技巧与空间布局策略,予以应对从而对各种不同的问题,亦能应变自如2. 掌握测试性能的方法,学会测算时/空交换的代价,客观评估自身的编程能力,13:34:09,3,第六章内容,内联函数 ( Inline Functions ) 数据结构 ( Data
2、Structures ) 算法 ( Algorithms ) 数值计算 ( Numerical Computation )STL算法 ( STL Algorithms ) 动态内存 ( Dynamic Memory ) 低级编程 ( Lower Programming ),13:34:09,4,1. 内联函数 ( Inline Functions ),做法:将一些反复被执行的简单语句序列做成小函数用法:在函数声明前加上inline关键字作用:不损害可读性又能提高性能,13:34:09,5,/= #include bool isDigit(char); / 小函数 int main( )for(
3、char c; cinc /=,频繁调用的函数:用昂贵的开销换取可读性,13:34:09,6,/= #include int main( )for(char c; cinc /=,内嵌代码:开销虽少,但可读性差,13:34:09,7,内联方式:开销少,可读性也佳,/= #include inline bool isDigit(char); / 小函数 int main( )for(char c; cinc /=,内联标记 放在函数声 明的前面,13:34:09,8,内联函数的使用经验:,函数体适当小,且无循环或开关语句,这样就使嵌入工作容易进行,不会破坏原调用主体如:排序函数不能内联程序中特别
4、是在循环中反复执行该函数,这样就使嵌入的代码利用率较高如:上例中的isDigit函数程序并不多处出现该函数调用,这样就使嵌入工作量相对较少,代码量也不会剧增,13:34:09,9,/= #include #include using namespace std; /- int calc1(int a, int b) return a+b; inline int calc2(int a, int b) return a+b; /- int main()int x1000, y1000, z1000;clock_t t = clock();for(int i=0; i1000*1000*1000;
5、 +i)zi = calc1(xi%1000, yi%1000);cout(clock()-t)/CLK_TCK“without inlinen“;t = clock();for(int i=0; i1000*1000*1000; +i)zi = calc2(xi%1000, yi%1000);cout(clock()-t)/CLK_TCK“with inlinen“; /=,性能测试,初记时,末记时,非内联函数,内联函数,13:34:09,10,结果分析: 内联用与不用差很多结论: 应尽量将函数改造成可内联性质,提高性能,E:ch06f0605 8.281 without inline 2.
6、437 with inline,13:34:09,11,2. 数据结构 ( Data Structures ),揭示:将数据结构实现为数据类型是编程的高级境界,STL容器就是系统提供的现成数据结构做法:充分和合理使用STL容器,简化复杂问题,提高编程效率与程序性能,13:34:09,12,问题:,有许多序列,每个序列都等待验证是否可从顺序数列经过栈操作而得例如:顺序数列 1,2,3,4,5待验证序列 3,2,1,5,4待验证序列 5,3,4,2,1,13:34:09,13,采用不同的数据结构,#include #include #include/ 栈方式: #include / 向量方式: #
7、includeusing namespace std;,13:34:09,14,栈方式,/= int main()ifstream in(“rail.txt“);for(int n,line=0; inn /=,栈中若不 存在读入 的元素, 则应入栈,创建栈,读入元素 不在栈顶 即为失败,退栈即逐 个逐个过,13:34:09,15,向量方式,/= int main()ifstream in(“rail.txt“);for(int n,line=0; inn /=,模仿栈操作,13:34:09,16,结论,不同的数据结构有不同的操作和性能应学习使用不同数据结构的经验,13:34:09,17,3.
8、 算法 ( Algorithms ),揭示:确定了数据结构之后,所采用的算法将直接决定程序的性能;任何语言都有个性,算法的选择使用是灵巧运用语言的艺术,充分发挥语言的优势是活用算法的根本 做法:培养经验,用测试的办法对算法进行选择,13:34:09,18,问题:,已知不太大的正整数n(n46),求Fibonacci数列0 1 1 2 3 5 8 13 21 34 的第n项的值 对于后面的四种算法:unsigned fibo1 (unsigned n);unsigned fibo2 (unsigned n);unsigned fibo3 (unsigned n);unsigned fibo4 (
9、unsigned n); 如何选择其中之一,第0项,第1项,第2项,13:34:09,19,算法:递归法 优点:算法简单,容易编程 缺点:栈空间负担过重,调用开销过大,unsigned fibo1 (unsigned n) if (n=1) return n;return fibo1(n-1) + fibo1(n-2); ,n=0或1,13:34:09,20,算法:迭代法 优点:节省空间,节省时间 缺点:编程相对复杂,unsigned fibo2 (unsigned n) int c=n;for (int a=0,b=1,i=2; i=n; +i)c=a+b, a=b, b=c;return
10、c; ,13:34:09,21,算法3:向量迭代法 优点:节省时间 缺点:n越大,占用堆空间越多,unsigned fibo3 (unsigned n) vector v(n+1, 0); v1 = 1;for (unsigned i=2; i=n; +i)vi = vi-1 + vi-2;return vn; ,13:34:09,22,算法4:直接计算法 优点:节省时间 缺点:引入了浮点计算,unsigned fibo4 (unsigned n) return ( pow ( (1+ sqrt ( 5.0 ) ) / 2, n) pow ( (1 sqrt ( 5.0 ) ) / 2, n)
11、 )/ sqrt ( 5.0 ) ; ,13:34:09,23,fibo1:只在示意性编程中使用,但并不是否定一切递归法 fibo2:在讲究性能的场合中使用,它省空间省时间,但在n很大的场合中,性能比不上fibo4 fibo3:可以数组代替向量,提高低级编程的性能,它易编易用,还可以读取中间项的值,但在一味追求性能的场合中,比不上fibo2 fibo4:在n不太大时,与fibo2相当,在n趋向很大时,其性能优势便充分体现,13:34:09,24,4. 数值计算 ( Numerical Computation ),揭示:在近似计算中,除了计算范围与终止计算条件外,还涉及逼近的快慢,这与算法有很大
12、关系,选择成熟的算法具有决定性作用 做法:了解各种数值计算算法的特点及使用场合,有的放矢解决实际问题,13:34:09,25,数值计算的参数描述,template / T为赖以计算的数系 T method ( / method为某种算法T a, / 左边界T b, / 右边界const T Epsilon, / 终止条件T ( * f ) ( T ) / 求值数学函数 );,13:34:09,26,矩形法,double rectangle(double a, double b, const double Eps, double (*f ) (double) ) double w=b-a, sN
13、 = w*( f (a) + f (b) ) / 2, sO=0;for ( int n=1; abs ( sN - sO ) = Eps; n*=2 ) sO = sN; sN = 0;for ( int i=0; in; +i )sN += f ( a + w * ( i+0.5 ) / n );sN *= w / n;return sN; ,小矩形逐个求和,前后两次小 矩形和的差,13:34:09,27,辛普生法,double simpson ( double a, double b,const double Eps, double (*f) (double) ) double I2n=
14、0,h=b-a,T2n=h*(f(a)+f(b)/2,In=T2n,Tn;for ( int n=1; abs( I2n In ) Eps; n+=n, h/=2.0 ) In=I2n; Tn=T2n; / In老积分值double sigma=0;for ( int k=0; kn; k+ )sigma += f ( a+(k+0.5)*h );T2n=(Tn+h*sigma)/2;I2n=(4*T2n-Tn)/3; / I2n新积分值return I2n; ,小矩形求和,辛普生处理,前后两次辛 普生值的差,13:34:09,28,性能比较,求同样的数学函数,区间和精度 矩阵法比辛普生法多循
15、环许多次,13:34:09,29,5. 标准+ 算法 ( Standard C+ Algorithms ),揭示:标准C+提供了诸多算法,这些算法的组合构成了许多问题的解,对算法的准确了解是编程能力的一大体现 做法:吃透标准+算法,灵活运用之,13:34:09,30,容器的区间表示,vector a (10); / 下面表示待处理的元素 vector b (a.begin ()+1, a.begin ()+7 );,0,1,2,3,4,5,6,7,8,9,首,尾,待处理的元素,13:34:09,31,逐一读入两个字串,比较是否含有相同元素,int main ( ) ifstream in (
16、“string.txt“ ) ;for (string s,t; inst; ) 比较输出 yes 或 no ,13:34:09,32,分别排序,直接加以字串比较是直截了当的思路:,for ( string s,t; inst; ) sort ( s.begin ( ), s.end ( ) ) ;sort ( t.begin ( ), t.end ( ) ) ;cout( s=t ? “yesn“ : “non“ ) ; ,C+标准算法,13:34:09,33,避免排序,分别统计两个字串中的个数则性能更优: ( 排序至少做O(nlogn)数量级运算,统计只做O(n)数量级运算 ),for (
17、 string s,t; inst; ) int s1=count(s.begin(), s.end(), 1);int s0=count(s.begin(), s.end(), 0);int t1=count(t.begin(), t.end(), 1);int t0=count(t.begin(), t.end(), 0);cout(s1=t1 ,C+标准算法,13:34:09,34,字串中非即的特征,可以避免多余的统计,进一步提高性能:,for ( string s,t; inst; ) int s1=count ( s.begin(), s.end(), 1) ;int t1=coun
18、t ( t.begin(), t.end(), 1) ;cout (s1=t1 ,C+标准算法,13:34:09,35,6. 动态内存 ( Dynamic Memory ),揭示:许多问题不知道数据量的大小,需要所运用的数据结构具有扩容能力;许多问题要求时间性能甚于空间占用充分利用堆空间(动态内存)是解决这些问题的关键 做法:理解堆空间的使用场合,学习堆空间的使用方法,13:34:09,36,使用容器,便是自动使用堆内存,例如,从abc.txt中读取诸段落:ifstream in ( “abc.txt“ ) ;vector ps ;/ ps.reserve(1100) ; 可以预留for (
19、string s ; getline ( in, s ) ; )ps.push_back(s) ;预留是减小频繁扩容造成的数据移动开销,13:34:09,37,若每个数据的处理,都要用到已经处理的数据时,保存历史数据,则可以改善时间性能,例如,统计一亿之内的素数个数(原始版):bool isPrime(int n)int sqrtn=sqrt(n*1.0);for(int i=2; i=sqrtn; +i)if(n%i=0) return false;return true; /- int main()int num=0;for(int i=2; i=100000000; +i)if(isPr
20、ime(i) num+;coutnumendl; /-,13:34:09,38,空间换时间版,int main() bitset* p = new bitset;p-set();for(int i=2; itest(i)for(int j=i*i; jsize(); j+=i)p-reset(j);int num=0;for(int i=2; itest(i)num+;coutnumendl;delete p; ,在空间中统计,完成每个 元素的标记,13:34:09,39,7. 低级编程 ( Lower Programming ),揭示:通过无原则的代码优化来简化程序的执行步骤,从而提高性能.
21、它会影响可读性,并需要对程序的内存布局有深刻的了解,这一般是编程课的后继学习内容 做法:尽量去掉组织程序所花费的代价:少用调用频繁的函数;通过指针间访数据;尽可能不用类;无原则数据共享;用C库不用C+库,13:34:09,40,低级编程版: 逐一读入两个字串,比较是否含有相同元素,int cnt(char* a)int num=0;while(*a) if(*a+=1) num+;return num; /- int main()freopen(“string.txt“,“r“,stdin);char a1025, b1025;whlie(scanf(“%s“,a)0 ,13:34:09,41
22、,STL容器是为方便编程设计的,它当然也考虑了性能,但与直接操纵堆空间相比还是间接了些,例如,一亿之内的筛法求素数个数(版):int sieveSTL()bitset ,13:34:09,42,一亿之内的筛法求素数个数(低级编程版):,int sieve()unsigned int* p=(unsigned int*)malloc(12500000);memset(p,-1,12500000);int num = 100000000-2;for(int i=2; i=10000; +i)if(pi/32 ,13:34:09,43,低级编程与高级编程的差异,低级编程与高级编程的差异在代码量上能够一定程度地反映出来,但根本的差异在于使用编程语句的抽象性程度 代码越抽象,其内在的数据与操作的安全性考虑得越多,因而额外开销就越大 反之,代码越低级,对数据的操作越直接,越需要程序员亲自驾驭程序的整体安全控制,