程序性能分析
在此文档中,内容包括但不限于:
- 空间复杂度,时间复杂度等概念定义;
- 性能的测量标准;
- 各种常见算法(可能特指排序算法?)的最好、最差情况;
- 略。
可能会牵涉的相关知识点(会在后续进行补充):堆、指针、栈等等。
概念定义
空间复杂度
空间复杂度主要考虑的是运行代码所占用的空间。其中,空间可大致分为:指令空间 (instruction space),数据空间 (data space),和环境栈空间 (enviroment stack space) 。指令空间是指编译之后程序代码所需要的存储空间,数据空间是指变量和变量值所需要的存储空间,环境栈用来保存暂停的函数和方法在恢复运行时所需要的信息(可以联想递归的过程进行记忆)。
对于指令空间和数据空间来说,比较容易进行理解。指令空间大致与指令的长度相关,对于数据空间的相关知识点,只需记住下面这个表即可。
数据类型 | 空间大小(单位为字节) | 范围 |
---|---|---|
bool(布尔值) | 1 | {true, flase} |
char(字符) | 1 | [-128, 127] |
unsigned char | 1 | [0, 255] |
Short(短整数) | 2 | [-32768, 32767] |
unsigned short(无符号短整数) | 2 | [0,. 65535] |
long | 4 | [ − 2 31 , 2 31 − 1 ] [-2^{31}, 2^{31} - 1] [−231,231−1] |
unsigned long | 4 | [ 0 , 2 32 − 1 ] [0, 2^{32} - 1] [0,232−1] |
int(整数) | 4 | [ − 2 31 , 2 31 − 1 ] [-2 ^{31}, 2^{31} - 1] [−231,231−1] |
unsigned int | 4 | [ 0 , 2 32 − 1 ] [0, 2 ^ {32} -1 ] [0,232−1] |
数据类型 | 空间大小(单位为字节) | 范围 |
---|---|---|
float(单精度浮点数) | 4 | ±3.4E±38 |
double(双精度浮点数) | 8 | ±1.7E±308 |
long double | 10 | ±1.2E±4392 |
pointer | 2 | (near,_cs,_ds,_es,_ss 指针) |
pointer | 4 | (far, huge 指针) |
ps:unsigned 本身不会改变数据类型的大小,仅仅是将有符号数变化为了无符号数。若用横轴来考虑,可以想作是将负半轴的长度抹去,将正半轴的长度 * 2。
另外,一个结构变量的空间大小是每个结构成员所需要的空间大小之和。
接下来简单阐述一下环境栈空间,当函数进行调用的时候,会把下列数据保存在环境栈中:
- 返回地址;
- 正在调用的函数的所有局部变量的值以及形式参数的值;
可以理解,返回地址是为了当递归函数进行到一定程度时,可以根据地址往回进行回溯,可以理解为找到回家的路(不是),而保存所有变量及参数的值则是为了保护现场。
时间复杂度
一个程序 P 所需要的时间是编译时间和运行时间之和。因为一个编译过的程序可以运行若干次而不用进行重新编译,因此我们一般只关注于程序的运行时间。
在统计时间复杂度的时候,可以通过 步数(step-count)方法来进行统计。每一步即一个程序步,可以大概地定义为一个语法或定义上的程序片段,该片段的执行时间独立于实例特征。
对于个人来说,在时间复杂度这个相关知识点中,比较重要的就是理解并且记住各个排序算法(如插入排序,合并排序,冒泡排序等)最好、最坏情况下的时间复杂度(或称作为 BIG O)。此部分内容会在下文出现。
大 O 记法
对于时间复杂度,通常有三种记法,但其中最常用的即是大 O 记法,下面对它的概念进行简要的一个阐述:
f ( n ) = O ( g ( n ) ) f(n) = O(g(n)) f(n)=O(g(n)),代表 f(n) 渐进小于或等于 g(n),即 g(n) 是 f(n) 的上限。
通常会出现的项数:
符号 | 含义 |
---|---|
O(big o) | 渐近小于或等于 |
Ω(omega) | 渐近大于或等于 |
Θ(theta) | 渐近等于 |
后两者在公式中的含义与大 O 记法中的定义相同,不再进行过多的描述。
常见的 BIG O
由于要介绍常见的排序算法的 BIG O,因此得简单介绍一下要出场的排序算法:
Algorithm | Description | Runtimes |
---|---|---|
bogo sort(猴子排序) | shuffle and pray | O ( n n ! ) O(nn!) O(nn!)(you won’t use it) |
bubble sort(冒泡排序) | swap adjacent pairs that are out of order | O ( N 2 ) O(N^2) O(N2) |
selection sort(选择排序) | look for the smallest element,move to front | O ( N 2 ) O(N^2) O(N2) |
insertion sort(插入排序) | build an increasingly large sorted front portion | O ( N 2 ) O(N^2) O(N2) |
merge sort(合并/归并排序) | recursively divide the data in half and sort it | O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N) |
heap sort(堆排序) | place the values into a binary heap then dequeue | O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N) |
quick sort(快速排序) | recursively “partition” data based on a pivot value | O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N) |
bucket sort(桶排序) | cluster elements into smaller groups,sort the groups | O ( n + m + n ( l o g n − l o g m ) ) O(n + m + n (logn - logm)) O(n+m+n(logn−logm)) |
radix sort(基数排序) | sort integers by last digit, then 2nd to last, then… | O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) |
(具体的排序算法介绍,请看后边的章节噢~)
性能测量
由于写者主要想复习数据结构的相关知识,故将这部分的内容暂且略过。但是在性能测量当中,需要注意一个知识点:
在性能测量当中,若设计到二维数组或三维数组的多次访问,则各个维度的下标访问顺序会严重的影响运行时间(尤其当 N 足够大时),若访问时间过长,主要是因为访问顺序与存储顺序不同,因此在高速缓冲中命中的数量大大减小,而导致不在高速缓冲(CACHE)中取数据而是去硬盘当中取,则最终影响了运行时间。