软件开发使用技术
1)构建脚本架,也就相当于写出测试用例
2)编码——用高级伪代码描绘出框架
3)测试——用第一步中的测试用例进行测试
4)调试
5)计时
性能
1. 提升程序运行速度
提升程序运行速度的常用方法:从设计层次着手。
1)问题定义
2)系统结构
将大系统分解为模块,进行“封底”估计,以确保其性能近乎符合要求。
3)算法和数据结构
4)代码优化
比如可以用单精度浮点数运算来代替双精度浮点数运算以加倍运行速度;可以将关键模块代码直接用汇编语言进行编写来提高运行速度。
5)系统软件
选用更高效的软件来替代低效的软件,比如对于查询操作而言,新数据库系统速度是否更快?
6)硬件
用运行速度更快的硬件。
具体实现方法:
- 通过宏定义或内联替换函数调用来压缩过程调用层次结构
- 循环展开
int search1(int t)
for i = [0,n)
if x[i] == t
return i;
else
return -1;
展开:
int ssearch2(int t)
x[n] = t;
for (i =0; ;i +=8)
if (x[i ] == t) { break;}
if (x[i+1] == t) {i += 1; break;}
if (x[i+2] == t) {i += 2; break;}
if (x[i+3] == t) {i += 3; break;}
if (x[i+4] == t) {i += 4; break;}
if (x[i+5] == t) {i += 5; break;}
if (x[i+6] == t) {i += 6; break;}
if (x[i+7] == t) {i += 7; break;}
if (i == n)
return -1
else
return i
- 利用代数恒等式来代替相对复杂的运算
eg.求余运算 %
源代码:
k = (j+rotdist) % n
修改为:
k = j + rotdist;
if (k> = n)
k -= n;
- 合并测试减少比较次数
eg.二分查找
初始:
l = 0; u = n-1;
loop
if l > u
p = -1;break;
m = (l+u) / 2
case
x[m] < t: l = m+1;
x[m] == t: p = m;break;
x[m] > t: u = m-1;
修改为:
l = -1; u = n;
while(l+1 != u)
m = (l+u) / 2
if x[m] < t
l = m
else
u = m
p = u;
if p>=n || x[p] != t
p = -1
当然还可以进行循环展开减少或者消除循环开销,也还可以利用代数恒等式将上下限的表示方法更改为下限和增量表示法。
2. 压缩空间
1)减少数据空间
- 不保存,重新计算
- 稀疏结构
- 信息理论
- 分配策略
- 函数定义(用函数代替代码中的公共模块以减少代码空间需求,也增加可读性)
- 解释器(将复杂代码用解释器替换)
for i = [17,43] set(i, 68)
for i = [18,42] set(i, 69)
for j = [81,91] set(30, j)
for j = [82,92] set(31, j)
这里,set(i, j)打开屏幕位置(i, j)处的图片元素,使用合适的函数,比如绘制水平线和垂直线的hor和ver函数,可以如下所示替换那段代码:
hor(17, 43, 68)
hor(18, 42, 69)
vert(81, 91, 30)
vert(82, 92, 31)
上述代码又可以用一个解释器来替换,这个解释器从类似下面的数组中读取命令:
h 17 43 68
h 18 42 69
v 81 91 30
v 82 92 31
- 转换成机器语言(如将大型系统中的关键部分直接用汇编语言进行手工编码)
3. 封底计算
“任何事情应该做到尽可能的简单,除非没有更简单的了”——爱恩斯坦。
“利特尔法则”——系统中物体的平均数量就是系统中物体离开系统的平均速率和每个物体在系统中停留的平均时间的乘积。比如某个酒徒在自己的存有150个盛满酒的容器,他每年会喝完(和买回)25个容器的酒,问每个容器要保留多少年?(结果是:150/25=6年)
封底计算也就是对软件运行时间进行估计,安全系数的引入可以弥补估计参数时可能的错误以及对手边问题的无知带来的意外情况。因此,在进行可靠性/可用性承诺时,应该对我们能满足的目标保留一个10的系数,以补偿未知情况;在估计规模、成本以及进度时,我们应该保留2或者4的系数,以弥补在某些方面设计上的缺陷。
著名名词:"back of the envelope"、"Fermi problem"。
通过算法减少程序运行时间示例:求输入向量中任何相邻子向量中的最大和,模型为一维模式识别。
输入:一个具有n个浮点数的向量 x;
输出:x 中任何子向量的最大和。(当所有输入都是负数时,最大子向量为空,其和为0)
eg.
输入:31 -41 59 26 -53 58 97 -93 -23 84
输出:x[2...6]子向量的和187
1)最简单的算法——迭代所有情形(时间复杂度 O(n^3))
maxsofar = 0
for i = [0,n)
for j = [i,n)
sum = 0
for k =[i,j]
sum += x[k] //sum of x[i...j]
maxsofar = max(sum, maxsofar)
2)利用前一步的结果(时间复杂度O(n^2))
maxsofar = 0;
for i = [0,n)
sum = 0;
for j = [i,n)
sum += x[j]; //sum of x[i...j]
maxsofar = max(sum, maxsofar);
3)分治算法(时间复杂度O(n*logn))
思想:将原始序列分解成左右两部分,则最大子向量和有3种情况:在左边部分;在右边部分;由两者合成。
float maxsum(l,u)
if (l>u) //zero element
return 0;
if(l==u) //one element
return max(x[l],0)
m = (l+u)/2;
lmax=0;rmax=0;
lsum=0;rsum=0
for(i=m; i>=l; i--)
lsum += x[i];
lmax = max(lsum, lmax);
for i=(m,u]
rsum += x[i];
rmax = max(rsum,rmax);
return max(maxsum(l,m),maxsum(m+1,u),lmax+rmax); //recursion
最初调用方式:float res = maxsum(0,n-1)。
4)扫描算法(时间复杂度O(n),为线性算法)
思想:从左端开始,一直扫描到最右端,记下碰到过的最大总和子向量。
maxsofar = 0;
maxendinghere = 0;
for i = [0,n)
maxendinghere = max(maxendinghere + x[i], 0);
maxsofar = max(maxendinghere,maxsofar);
eg.
x: 31 -41 59 26 -53 58 97 -93 -23 84
i=0: maxendinghere = 31; maxsofar = 31.
i=1: maxendinghere = 0; maxsofar = 31.
i=2: maxendinghere = 49; maxsofar = 49.
i=3: maxendinghere = 75; maxsofar = 75.
i=4: maxendinghere = 22; maxsofar = 75.
i=5: maxendinghere = 80; maxsofar = 80.
i=6: maxendinghere = 187; maxsofar = 187.
i=7: maxendinghere = 94; maxsofar = 187.
i=8: maxendinghere = 71; maxsofar = 187.
i=9: maxendinghere = 155; maxsofar = 187.
所以,最终结果为187.