您的位置:首页 > 编程语言

编程珠玑<续>之性能监视工具3-验证篇

2014-03-18 17:41 295 查看
今天还是继续讨论第一章(这效率-_-|||)。其实已经看到第三章了,只不过看到后面越发觉得不太对劲,这书不该这么看啊,怎么能一过了之呢?太对不起作者了好吧(虽然这本书很薄看起来很容易看完的样子)。更何况,而且明明很多都没有弄懂,于是有了现在这个验证篇。

1.利用gcov对书中素数例子进行验证

尽管知道书上说的肯定是对的,可是还是想自己动手试试看结果。

首先,编译 加入gcov的参数

g++ prime.cpp -fprofie-arcs -ftest-coverage -o prime


运行prime后 ,代码覆盖率的测试数据已经收集在了gcda文件中,再用gcov 生成结果。

gcov prime
下面是书上关于素数几个例子的验证。

P1 简单版本

999:    7:int prime(int n){
999:    8:	int i;
78190:    9:	for( i=2;i<n;i++){
78022:   10:		if(n%i==0){
831:   11:			return 0;
-:   12:		}}
168:   14:	return 1;}

P2 只检验平方根

5456:    6:int root(int n){       //被调用5456次和for循环次数一样
5456:    7:	return (int) sqrt((float)n);
-:    8:}
999:   10:int prime(int n){
999:   11:	int i;
5456:   12:	for( i=2;i<=root(n);i++){
5288:   13:		if(n%i==0){
831:   14:			return 0;
-:   15:		}}
168:   17:	return 1;}

-:   23:int main()
1:   24:{
1:   25:    int i,n;
1:   26:    n=1000;
1000:   27:    for(i = 2; i <= n; i++)
999:   28:		if (prime(i))
168:   29:		cout<<i<<endl;
1:   31:return 0;
3:   32:}


P3只计算一次开方

999:    6:int root(int n){
999:    7:	return (int) sqrt((float)n);
-:    8:}
999:   11:int prime(int n){
999:   12:	int i,bound;//用bound存储root开方结果,减少root被调用次数,这里root在被调用999次
999:   13:    bound= root(n);
5456:   14:	for( i=2;i<=bound;i++){
5288:   15:		if(n%i==0){
831:   16:			return 0;
-:   17:		}}
168:   19:	return 1;}

-:   24:int main()
1:   25:{   int i,n;
1:   27:    n=1000;
1000:   28:    for(i = 2; i <= n; i++)
999:   29:		if (prime(i))  {
168:   31:			cout<<i<<endl;	}
1:   34:return 0;
3:   35:}
P4 特殊情况 2、3、5
由于3/4的合数都包含了2,3,5,因此特殊考虑。不过下面这个程序有问题

265:    6:int root(int n){
265:    7:	return (int) sqrt((float)n);
-:    8:}
999:   12:int prime(int n){
999:   13:	int i,bound;
999:   15:	if(n%2==0) return 0;
499:   16:	if(n%3==0) return 0;
332:   17:	if(n%5==0) return 0;
-:   18:
265:   19:	bound= root(n);
1695:   20:	for( i=7;i<=bound;i = i+2){
1530:   21:		if(n%i==0){
100:   22:			return 0;
-:   23:		}}
165:   25:	return 1;
-:   26:}
-:   27:
-:   30:int main()
1:   31:{   int i,n;
1:   33:    n=1000;
1000:   34:    for(i = 2; i <= n; i++)
999:   35:		if (prime(i))  {
165:   36:			cout<<i<<endl;
-:   37:		}
1:   38:return 0;
3:   39:}


以上程序存在问题,没有将素数:2,3,5检查出来 ,正确检验:

999:   11:int prime(int n){
999:   12:	int i,bound;
-:   13:
999:   14:	if(n%2==0) return (n==2);
499:   15:	if(n%3==0) return (n==3);
332:   16:	if(n%5==0) return (n==5);
-:   17:
1695:   18:	for( i=7;i*i<=n;i = i+2){
1530:   19:		if(n%i==0){
100:   20:			return 0;
-:   21:		}}
165:   23:	return 1;
-:   24:}
-:   25:
-:   27:int main()
1:   28:{
1:   29:    int i,n;
1:   30:    n=1000;
1000:   31:    for(i = 2; i <= n; i++)
999:   32:		if (prime(i))  {
168:   33:			cout<<i<<endl; //上面这里是165次,没有检验2,3,5,这里将这个三个数也考虑进来了
-:   34:		}
1:   36:return 0;
3:   37:}

P6 用乘法替代开方

for( i=7;i*i<=n;i = i+2)
速度加快了。

习题2 中利用埃氏筛选法,n这里我取的还是1000为了和上面的做比较

1:    6:int main(){
1:    7: int i, p, n;
1:    8:char x[1002];
1:   10: n = 1000;
-:   11:
1001:   12:for (i = 1; i <= n; i++)
1000:   14:   x[i] = 1;
1:   15:   x[1] = 0;
1:   16:   x[n+1] = 1;
1:   18:   p = 2;
169:   20:   while (p <= n) {
168:   21:     cout<<p<<" ";
2126:   23:       for (i = 2*p; i <= n; i = i+p)
1958:   25:              x[i] = 0;
999:   27:          do
999:   28:              p++;
-:   30:            while (x[p] == 0);
-:   31:    }


2.求解习题1 (很有意思的一道题, 我可以说我之所以想写这篇文章主要是为了这道题。)

题目:假设数组X[1...1000]中散布着随机实数。下面这个例程计算最小和最大值。

Max:= Min:=X[1]
for I:=2 to 1000 do
if X[I] >Max then Max := X[I]
if X[I]<Min  then  Min  := X[I]


B.C.Dull 先生注意到,如果一个元素是新的最大值,则这个元素不可能是最小值。因而他把两次比较写成

if X[I] >Max then Max := X[I]
else if X[I]<Min  then  Min  := X[I]
这样平均起来将节省多少次比较?先猜答案,再通过实现和监控程序性能功能来找出答案。你猜的怎么样?

我当然没有猜到答案,看了书上给的答案后还是觉得不太清楚,可能还是和翻译有关吧。然后,自己先动手验证了一番。

-:    1:#include<stdio.h>
-:    2:#include<stdlib.h>
-:    3:#include<iostream>
-:    4:#include<time.h>
-:    5:#define random() (rand()/(double)(RAND_MAX))
-:    6:using namespace std;
-:    7:
1:    8:void MinMax(double a[],int n){
1:    9:	double max=a[0],min=a[0];
1000:   10:	for(int i=1;i<n;i++){
999:   11:    if(a[i]>max) max=a[i];
999:   12:	if(a[i]<min) min=a[i];  //999
-:   13:	}
1:   14:	cout<<"original min:"<<min<<"original max:"<<max<<endl;
-:   15:}
-:   16:
1:   17:void MinMax2(double a[],int n){
1:   18:	double max=a[0],min=a[0];
1000:   19:	for(int i=1;i<n;i++){
999:   20:    if(a[i]>max) max=a[i];
995:   21:	else if(a[i]<min) min=a[i];} //995
1:   23:	cout<<"Optimized min:"<<min<<"original max:"<<max<<endl;
-:   24:}
-:   25:
1:   26:int main(){
1:   27:	int n=1000;
1:   28:	double a[1000];
1:   29:	srand((int)time(0));
1001:   30:	for(int i=0;i<n;i++){
1000:   31:     a[i]=rand();}
1:   33:   MinMax(a,n);
1:   34:   MinMax2(a,n);
1:   35:return 0;
3:   36:}


我也在N=1000的条件下将程序运行了15遍,排序后的赋值数依次为:

3 4 4 5 5 6 7 7 7 7 7 8 9 9 9

我这里的结果是6.47,果然和理论给的期望值6.485很接近。

那么到底为什么答案是这样呢?

书上是这么说的,knuth在The Art of Computer Programming,Volume1:Foundamental Algorthms的1.2.10中说明了该算法在平均情况下少进行Hn-1次赋值,其中

Hn=1+1/2+1/3+...+1/n,是一个调和级数。而该调和级数对于特定的n值收敛于ln(n)+r,r是欧拉常数约等于0.577。于是有 ln(1000)+0.577-1 =6.485。

也就是说比较1000的情况下,平均省7次左右。

究竟为什么是调和级数呢?我没有看那本书,但网上关于这个题的说法不多,有个听起来有点意思,说的是减少的次数等于第一个条件命中的次数(?)。第n个数为最大值的概率是1/n,那么所有第一个条件命中的次数概率和为:1/n+1/(n-1)+...+1/1=ln(n)+r。是这样么?

还有一个人用了更为严谨的证明如下:

令随机变量Xi对应于Ai>MAX这个事件有:Xi={1 如果 Ai>MAX ,0 如果Ai<MAX}

令随机变量X表示少比较的总次数

X=X2+X2+...+Xn

问题转化为求X的期望值

因为A是随机排列,Ai是前i个值中最大值的概率为1/i

E(Xi)=1*(1/i)+0*(1-1/i)=1/i

E(X)=E[X2+X2+...+Xn]=E(X2)+E(X3)+E(X4)+...+E(Xn) =1/2+1/3+...+1/n=ln(n)+O(1)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: