存储模型(下)

程序的局部性:

程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域

时间局部性
一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时期内
空间局部性
当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都集中在一个较小区域内
分支局部性
一条跳转指令的两次执行,很可能跳到相同的内存位置

例子:

页面大小为4K,分配给每个进程的物理页面数为1。在一个进程中,定义了如下的二维数组int A[1024][1024],该数组按存放在内存,每一行放在一个页面中(1024 * 4 = 4K)

程序编写方法1:
for (j = 0; j < 1024; j++)
for (i = 0; i < 1024; i++)
      A[i][j] = 0;
程序编写方法2:
for (i=0; i<1024; i++)
for (j=0; j<1024; j++)
          A[i][j] = 0;

两种数组初始化程序,第一种是按照对每列初始化,第二种是对每行进行初始化

第一种先访问a0,0,缺页将第0页调入内存,然后访问a1,0缺页,由于内存只分配给该进程只有1个页框,所以将第0页调出,第1页调入,一次类推,可得缺页总次数1024*1024

第二种先访问a0,0,缺页将第0页调入内存,然后访问a0,1,a0,1在第0页中,此时还在内存中没有缺页,只有访问下一行是才发生缺页,所以以此类推缺页总次数1024次

虚拟存储技术:

当进程运行时,先将其一部分装入内存另一部分留在磁盘,当要执行的指令或访问数据不在内存时,由OS自动完成将它们从磁盘调入内存进行工作

虚拟地址空间即为分配给进程的虚拟内存

虚拟地址是在虚拟内存中指令或数据的位置,该位置可以被访问,就好像它是内存的一部分

虚存:

内存磁盘有机结合使用得到一个很大内存。

虚存是对内存的抽象,构建在存储体系之上,由操作系统协调各存储器的使用

虚存提供了一个比物理内存空间大得多的地址空间

总结:

虚存的应用背景是用小内存运行大程序。这里的"大程序"是指比整个内存用户空间还要大的程序,它可以是一道程序,也可以是多道程序之和。

虚存的可行基础是程序运行的局部性原理。

实现虚存的主要技术是部分装入、部分对换、局部覆盖、动态重定位。

存储保护:

确保每个进程独立的地址空间
确保进程访问合法的地址范围
确保进程的操作是合法的

cpu将逻辑地址与(进程空间的基地址+界限寄存器存的进程空间长度)进行比较,一旦不满足条件就会导致异常CPU控制权交给OS处理

虚拟页式:

虚拟存储技术 + 页式存储管理方案
→ 虚拟页式存储管理系统

基本思想:

1.进程开始运行之前,不是装入全部页面,而是装入一个或零个页面
2.之后,根据进程运行的需要,动态装入其他页面(请求调页,预先调页方式)
3.当内存空间已满,而又需要装入新的页面时,则根据某种算法置换内存中的某个页面,以便装入新的页面

主要就是用CPU时间和磁盘空间换取内存空间

页表的设计(一般由硬件完成):

页表项(页号是下标即可):

1.页框号

2.有效位:表示该页是在内存还是在磁盘上 0:磁盘,1:内存

3.访问位:引用位,表示是否访问过

4.修改位:表示该页在内存中是否被修改过

5.保护位:读/可读写

思考:

32位虚拟地址空间的页表规模?页面大小为4K;页表项大小为4字节

则:一个进程地址空间有 ?页(需要知道32位,即进程被创建时就从虚存中分配给它2 ^ 32 = 4GB地址空间,4GB/4K = 2^20页)

其页表需要占 ?页(2^20页就需要有2^20个页表项,2^20 * 4 / 4K = 1024页)

64位虚拟地址空间页面大小为4K;页表项大小为8字节,同理页表规模: 32,000 TB

可以看到64位的页表规模太大,但是64位寻址空间为2^ 64 = 16TB,若32000TB作为单级页表,CPU根本就无法完全访问,而且也没有这么大的内存

此时就要引入多级页表:

此时使用多级页表的好处:它可以被动态的加载页表有需要的时候,从硬盘上加载进内存,从这一点上来看确实是比单级页表一次性全部加载节省了内存,但是多级页表总量上并没有比单级页表减少,因为多级页表还是要那么多页表项并且还要算上之前的链接比单级的大多了。

页表页在内存中若不连续存放,则需要引入页表页的地址索引表 → 页目录(Page Directory)

二级页表可以表示4GB的空间(2^10页表,每个页表保存2^10项,总共可以记录2^20页,2^20*4K (页面大小)= 4GB)

vpn:virtual page number

PS.i386页目录项与页表项

反置页表:

附上知乎上R大链接:http://www.cs.virginia.edu/~cs333/notes/virtual_memory4.pdf

产生反置页表原因:

例子:

64位虚拟空间,4KB的页面大小,512MB物理内存,那么对于单级页表需要多少空间来存储?

进程地址空间2^64B,页面大小2^12B 推出2^52页,表明单级页表需要有有2^52项

页表项大小:512MB = 2^29B 推出页框总数为2^17页,那么对于简单的页表项中,17位用来表示页框号加上用1字节表示的入口控制位总共需要4字节

所以页表大小为2^52 * 4 = 2^54B

一个页面4K,一个页表项为4B所以一个页表最多1024项 = 2 ^10,所以(2^10)^ 6 > 2 ^ 52,即至少需要6级页表,但是多级页表总的大小还是大于单级页表所以不考虑

此时我们是靠的页表都是从虚拟页号对应到物理页号,为什么我们不能用物理页号无对应虚拟页号呢?因为物理页号比较少

所以引出反置页表:页表项为进程PID和对应的虚拟页号,整个页表为大数组,下标为物理页号(注意还是从虚拟页号转化成物理页号)

假设:pid占16位,虚拟页号占52位(因为之前算出的虚拟页数2^52页),入口控制位为12位,总共页表项为8byte * 物理页数2^17 = 1.3MB远小于单级页表

根据反置页表,从头到尾找对应的pid和虚拟页号,从而下标即为物理页号,但是这里出现了问题,从头到尾找效率不高,所以又加上hash算法

首先pid和vpn(虚拟页号)通过hash函数找到页表对应的下标,发现vpn和pid不符,可能之前hash出来的值发生碰撞,经过碰撞处理函数后移了,所以通过next找到下一个相同hash值,发现vpn和pid对上了所以此时下标即为物理页号

快表(TLB)引入:

页表 → 两次或两次以上的对相同的内存地址访问
CPU的指令处理速度与内存指令的访问速度差异大,CPU的速度得不到充分利用

这问题跟CPU与内存之间为了提升效率,在CPU和内存之间加上cache原理类似。

快表:

一种随机存取型存储器,除连线寻址机制外,还有接线逻辑,能按特定的匹配标志在一个存储周期内对所有的字同时进行比较。

性质:相联存储器(associative memory)

特点:

按内容并行查找,保存正在运行进程的页表的子集(部分页表项)(就不经过查整个页表来得到页框号,直接利用较小的快表得到页框号快速访问内存)

引入快表后的地址转换:

首先mmu通过查找较小的快表,如果命中,那么直接页框号和页内偏移即可得到物理地址,如果不命中,那么只能通过查找页表,若对应的页表项中的有效位为0,即该页不在内存中,则发生page fault(缺页),CPU控制权交给OS,让OS从硬盘上调入相应的页到内存中。

页错误(page fault)

又称 页面错误、页故障、页面失效

原因

地址转换过程中硬件产生的异常

具体原因

1.所访问的虚拟页面没有调入物理内存;例如缺页异常

2.页面访问违反权限(读/写、用户/内核)

3.错误的访问地址,例如访问进程地址空间的空白区域

缺页异常:

是一种Page Fault,在地址映射过程中,硬件检查页表时发现所要访问的页面不在内存,则产生该异常——缺页异常

解决方案:

操作系统执行缺页异常处理程序:获得磁盘地址,启动磁盘,将该页调入内存
如果内存中有空闲页框,则分配一个页框,将新调入页装入,并修改页表中相应页表项的有效位及相应的页框号
若内存中没有空闲页框,则要置换内存中某一页框;若被置换的页框内容被修改过,则要将其写回磁盘

驻留集:

驻留集大小:给每个进程分配多少页框?

固定分配策略

进程创建时确定,可以根据进程类型(交互、批处理、应用类)或者基于程序员或系统管理员的需要来确定,一旦确定无法改变

可变分配策略
根据缺页率评估进程局部性表现
1.缺页率高→增加页框数
2.缺页率低→减少页框数
3.系统开销

PS.分配页框号和缺页率的关系

置换问题:

置换范围:

1.局部置换策略
  仅在产生本次缺页的进程的驻留集中选择
2.全局置换策略
  将内存中所有未锁定的页框都作为置换的候选

对于一个可变分配且实用局部置换策略的OS:

1.当一个新进程装入内存时,给它分配一定数目的页框,然后填满这些页框

2.当发生一次缺页异常时,从产生缺页异常进程的驻留集中选择一页用于置换

3.不断重新评估进程的页框分配情况,增加或减少分配给它的页框,以提高整体性能

最佳置换策略:

置换最近最不可能访问的页,这个思想主要是基于过去的行为来预测将来的行为,但是一旦置换策略越精细,软硬件开销就越大。

有个约束条件:

不能置换被锁定的页框

页框锁定:

给每一页框增加一个锁定位,通过设置相应的锁定位,不让操作系统将进程使用的页面换出内存,避免产生由交换过程带来的不确定的延迟

这些页框可能为:操作系统核心代码、关键数据结构、I/O缓冲区

清除策略:

清除:从进程的驻留集中收回页框

虚拟页式系统工作的最佳状态:发生缺页异常时,系统中有大量的空闲页框

结论:在系统中保存一定数目的空闲页框供给比使用所有内存并在需要时搜索一个页框有更好的性能

分页守护进程(paging daemon):

多数时间睡眠着,可定期唤醒以检查内存的状态
如果空闲页框过少,分页守护进程通过预定的页面置换算法选择页面换出内存
如果页面装入内存后被修改过,则将它们写回磁盘,分页守护进程可保证所有的空闲页框是“干净”的,没有被修改过的(一旦修改过的就要在往这个页框调入逻辑页之前,把这个页框内容写回磁盘)

当进程需要使用一个已置换出的页框时,如果该页框还没有被新的内容覆盖,将它从空闲页框集合中移出即可恢复该页面(即被置换后的页框如果还没有被新内容覆盖,会属于空闲页框集合中,还是在内存中,再次使用调出即可)

页缓冲技术:

不丢弃置换出的页,将它们放入两个表之一:如果未被修改,则放到空闲页链表中,如果修改了,则放到修改页链表中
被修改的页定期写回磁盘(不是一次只写一个),这大大减少I/O操作的数量,从而减少了磁盘访问时间
置换的页仍然保留在内存中,一旦进程又要访问该页,可以迅速将它加入该进程的驻留集合(代价很小)

置换算法:

最佳页面置换算法:

置换以后不再需要的最远的将来才会用到的页面

但是很难实现,只能作为其他算法的参考

先进先出算法:

选择在内存中驻留时间最长的页并置换它

实现:页面链表法(某个进程进入内存挂到该链表末尾,每次置换掉链表头对应的页框)

第二次机会算法:

对FIFO算法的改进,按照先进先出算法选择某一页面,检查其访问位R,如果为0,则置换该页;如果为1,则给第二次机会,并将访问位  置0,将其挂到链尾,从头接着再往下寻找

时钟算法:

对第二次算法的改进,第二次算法中摘下头放到链尾有开销,所以针对其进行改进

左图页框指针指向2,发现2这个页被访问过,将访问位置0,指针下移,可以看到被置0的那一页就相当于掉到了链尾,3页同理,发现4页没被访问过,将4页置换然后将访问位置0,之后指针下移,一次置换结束

最近未使用算法(NRU):

思想:选择在最近一段时间内未使用过的一页并置换

实现:设置页表表项的两位访问位(R), 修改位(M)(这些由硬件完成若不行就软件完成),启动一个进程时,R、M位置0,R位被定期清零(复位,因为如果有段时间没被访问就置零)

发生缺页中断时,操作系统检查R,M:
第1类:无访问,无修改
第2类:无访问,有修改
第3类:有访问,无修改
第4类:有访问,有修改

算法思想:
随机从编号最小的非空类中选择一页置换

对NRU算法的时钟算法实现:

1.从指针的当前位置开始,扫描页框缓冲区,选择遇到的第一个页框(r=0;m=0)用于置换(本扫描过程中,对使用位不做任何修改)

2.如果第一步找不到r=0,m = 0的页框,则重新回到初始地方扫描,选择第一个(r=0;m=1)的页框(本次扫描过程中,对每个跳过的页框,将其使用位设置成0)

3.如果第2步没找到r = 0,m = 1的页框,指针将回到它的最初位置,并且集合中所有页框的使用位均为0(被第二步复位)。重复第1步,并且,如果有必要,重复第2步

最近最少使用算法(LRU):

设计思想:选择最后一次访问时间距离当前时间最长的一页并置换,即置换未使用时间最长的一页

性能:接近OPT

实现:对每个页框打上时间戳 或 维护一个访问页的栈,但是对于那么多的页框系统开销大

LRU硬件实现:

对于内存只有0,1,2,3页面选择置换

访问顺序:0, 1, 2, 3, 2, 1, 0, 3, 2, 3

将0行置1,0列置0,有先后顺序,之后类似,最终将每行结果加起来,置换和最小的对应的页。

这种方法开销很大,因为如果页面变多,这个矩阵也变大,硬件实施也困难

最不经常使用算法:

选择访问次数最少的页面置换,LRU的一种软件解决方案

实现:
1.软件计数器,一页一个,初值为0
2.每次时钟中断时,计数器加R
3.发生缺页中断时,选择计数器值最小的一页置换

老化算法(对软件LRU的改进):

设计思想:

计数器在加R前先将计数器值右移一位,R位加到计数器的最左端

第一次0,1,2,3,4,5对应的访问位为1,0,1,0,1,1所以计数器值分别为0页右移左加1,1页右移一位,。。。。,最终计数器值小的对应的页面被置换出去。

例子:
系统给某进程分配3个页框(固定分配策略),初始为空
进程执行时,页面访问顺序为:2 3 2 1 5 2 4 5 3 2 5 2

FIFO算法:

首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出最早进入的2,之后2进入缺页而且要置换,换出最早进入的3,4进入缺页并置换,换出最早进入的1,5进入不缺页,3进入缺页并置换,换出最早进入的5,2进入不缺页,5进入缺页并置换,换出最早进入的2,2进入缺页并置换,换出最早的4

缺页次数:9

LRU算法:

首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出最近没使用的3,之后2进入不缺页,4进入缺页并置换,换出最近没使用的1,5进入不缺页,3进入缺页并置换,换出最近没使用的2,2进入缺页并置换,换出最近没使用的4,5进入不缺页,2进入不缺页(技巧:就看页框数多少,然后当前页往回退多少就置换该页面,例如:第一次5缺页,换出往前推3个的第3页)

缺页次数:7

OPT算法:

首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出之后没使用的1,之后2进入不缺页,4进入缺页并置换,换出之后没使用或者使用跨度最大的的2,5进入不缺页,3进入不缺页,2进入缺页并置换,换出之后没使用的4,5进入不缺页,2进入不缺页

缺页次数:6

由此看来LRU算法是最接近最优算法

BELADY现象:

1 2 3 4 1 2 5 1 2 3 4 5访问页面顺序
采用FIFO算法,计算当 m=3 和 m=4 时的缺页中断次数
m=3时,缺页中断9次;m=4时,缺页中断10次。

BELADY:FIFO页面置换算法会产生异常现象,即:当分配给进程的物理页面数增加时,缺页次数反而增加

工作集算法:

影响缺页次数因素:

页面置换算法
页面本身的大小 (页面设计如果太小,指令就少,需要频繁从磁盘加载页面到内存增加了缺页次数,太大内存中并发程序数目减小,效率下降)
程序的编制方法 (参见程序局限性原理)
分配给进程的页框数量

颠簸(Thrashing,抖动)
虚存中,页面在内存与磁盘之间频繁调度,使得调度页面所需的时间比进程实际运行的时间还多,这样导致系统效率急剧下降,这种现象称为颠簸或抖动

工作集算法:

设计思想:

根据程序的局部性原理,一般情况下,进程在一段时间内总是集中访问一些页面,这些页面称为活跃页面,如果分配给一个进程的物理页面数太少了,使该进程所需的活跃页面不能全部装入内存,则进程在运行过程中将频繁发生缺页中断,如果能为进程提供与活跃页面数相等的物理页面数,则可减少缺页中断次数

工作集模型:

一个进程在执行的页框集合

工作集W(t,Δ) = 该进程在某时刻t的过去的Δ个虚拟时间单位中访问到的页面的集合

内容取决于三个因素:
1.访页序列特性,访问序列不同得到的工作集也不同
2.时刻t
3.工作集窗口长度(Δ),窗口越大,工作集就越大

例子:
26157775162341234443434441327
| ------10-----|t1        |------10-----|t2
W(t1,10)={1,2,5,6,7}
W(t2,10)={3,4}

基本思路:
找出一个不在工作集中的页面并置换它(置换不是在某时刻t的过去的Δ个虚拟时间单位中的页面

思想:
1.每个页表项中有一个字段:记录该页面最后一次被访问的时间
2.设置一个时间值T
3.判断:
根据一个页面的访问时间是否落在“当前时间-T”之前或之中决定其在工作集之外还是之内

实现:

扫描所有页表项,执行操作
1. 如果一个页面的R位是1,则将该页面的最后一次访问时间设为当前时间,将R位清零
2. 如果一个页面的R位是0,则检查该页面的访问时间是否在“当前时间-T”之前
(1) 如果,则该页面为被置换的页面
(2) 如果不是记录当前所有被扫描过页面的最后访问时间里面的最小值(离当前最远的页面)。扫描下一个页面并重复1、2

内存映射文件:

基本思想:
进程通过一个系统调用(mmap)将一个文件(或部分)映射到其虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是对文件进行读写

特点:

在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时,页面才会被每次一页的读入,磁盘文件则被当作后备存储
进程退出或显式地解除文件映射时,所有被修改页面会写回文件

操作都在进程虚拟地址空间上,并且只有该页面要被访问时才将磁盘文件内容加载进来,而且只有等进程结束时才会把修改后的文件写回给磁盘

写时复制技术:

可以看到两个进程由于一样而共享物理页,这就相当于Linux调用fork函数是父子进程的关系,当子进程要往页面写入东西时,被OS捕捉,OS会在此时创建出与父进程一样的页面例如页面2,新复制的页面对执行写操作的进程是私有的,对其他共享写时复制页面的进程是不可见的(复制的页面2对子进程私有,对父进程不可见)

 作者水平有限,文章肯定有错还请各位指点!!!感谢!!!

参考链接:https://support.microsoft.com/zh-cn/help/2160852/ram--virtual-memory--pagefile--and-memory-management-in-windows

https://www.nowcoder.com/questionTerminal/d7f5fe2063e64573819f36484caf0beb

时间: 2024-10-09 05:07:14

存储模型(下)的相关文章

Bitcask 存储模型的实现 - merge与hint文件

在<Bitcask存储模型的实现 - 基本框架>中,我们了解了Bitcask存储模型中数据的存储方式.内存索引的组织形式,以及如何使用缓存加速数据读取.另外,Bitcask存储模型中提出闲时进行merge减少数据冗余.运用hint文件加速创建内存索引,下面我们来看merge的具体实现.如何用hint文件加速索引创建. merge Bitcask是日志型存储模型,对于新增和更改的操作,都Append到磁盘,磁盘使用率将随着操作的增多而增长. 以上图例表示,key及对应val最开始存放在25.w,

剖析Elasticsearch集群系列第一篇 Elasticsearch的存储模型和读写操作

剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例. 本文是这个系列的第一篇,在本文中,我们将讨论的Elasticsearch的底层存储模型及CRUD(创建.读取.更新和删除)操作的工作原理. Elasticsearch是当今最流行的分布式搜索引擎,GitHub. SalesforceIQ.Netflix等公司将其用于全文检索和分析应用.在Insight,我们用到了Elasticsearch的诸多不同功能,比如: 全文检索 比如找

多核程序设计——存储模型

最近在看<现代体系结构上的UNIX系统--内核程序员的SMP和Caching技术>,这里抄点东西作为笔记吧! 顺序存储模型强制存储器操作(load和store)都按照程序次序来执行,即这些指令是按照在随程序执行的指令流中出现的顺序次序来执行的.它也指定了,由不同处理器完成的load和store操作也要以某种顺序.但又是非确定性的方式排序.这种存储模型应该是大家最容易理解的,甚至都认为实际MP也是这样工作的.但是,这种存储结构是非常落后的,现代处理器应该已经淘汰了这种结构. 书中讲到了一个例子,

智能合约从入门到精通:Solidity语法之内存变量的布局和状态变量的存储模型

简介:在前面我们已经讲过Solidity语言的一些语法内容,在矩阵元JUICE开放平台的JIDE开发时,一定要注意Layout in Memory和Layout of State Variables in Storage,即内存变量的布局和状态变量的存储模型.内存变量的布局(Layout in Memory) Solidity预留了3个32字节大小的槽位: 0-64:哈希方法的暂存空间(scratch space) 64-96:当前已分配内存大小(也称空闲内存指针(free memory poi

第16章 CSS盒模型下

第 16章 CSS盒模型[下]学习要点:1.元素可见性2.元素盒类型3.元素的浮动 本章主要探讨 HTML5中 CSS盒模型,学习怎样了解元素的外观配置以及文档的整体布局. 一.元素可见性使用visibility属性可以实现元素的可见性,这种样式一般可以配合 JavaScript来实现效果.样式表如下:属性 visibility 值 说明 CSS版本visible 默认值,元素在页面上可见 2hidden 元素不可见,但会占据空间. 2collapse 元素不可见,隐藏表格的行与列. 2 如果不

层叠HMM-Viterbi角色标注模型下的地名识别

命名实体识别中最难的部分当属实体机构名了,这是因为机构名的组成成分十分复杂,可以是人名.地名.序数词.企业字号甚至是上级机构名.本文介绍一种基于角色标注的层叠HMM模型下中文机构名识别方法.目前代码已整合到HanLP中,即将开源.原理基本原理请参考<实战HMM-Viterbi角色标注地名识别>,不再赘述.与人名和地名识别稍有不同的是,在命名实体识别之前,需要先执行人名和地名识别,将粗分结果送入HMM模型求解,得出细分结果后才能进行,这是因为人名和地名也是机构名中的常见成分.这是与<实战H

第九章 用多线程来读取epoll模型下的客户端数据

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include

Bitcask存储模型

----<大规模分布式存储系统:原理解析与架构实战>读书笔记 近期一直在分析OceanBase的源代码,恰巧碰到了OceanBase的核心开发人员的新作<大规模分布式存储系统:原理解析与架构实战>.看完样章后决定入手,果然物有所值. 对于准备学习分布式的同学,这是一本不错的书籍,相对系统,全面的介绍了分布式的相关技术和项目,基本都是干货. 另一半是在介绍OceanBase的内容,对我来说,正是踏破铁鞋无觅处.接下来会有几篇专门研究存储引擎的读书笔记哟.废话不多说,转入正题. 1.存

层叠隐马模型下的音译人名和日本人名识别

命名实体中的人名识别包括中国人名,音译人名和日本人名.比如"北川景子参演了林诣彬导演,克里斯·摩根编剧的<速度与激情3>""林志玲亮相网友:确定不是波多野结衣?".以前用三层HMM做过中国人名的识别,取得了满意的效果.这次来实现另外两种人名的自动识别.音译人名常用字法音译人名按照地域又可以分为欧美人名和俄罗斯人名,两者的常用字差别较大.不过,由于翻译人名中使用的汉字相对固定,所以只需整理一个常用字库就可以实现基本的识别,比如:欧美人名常用字--·--阿埃

比较模型下的排序算法总结

说到算法,排序算法可能是大部分人最早接触的算法,我还记得我接触的第一个排序算法可能就是冒泡排序(bubble sort)了,估计不少同学和我一样吧.再后来接触到了插入排序(insertion sort),归并排序(merge sort),快速排序(quick sort),堆排序(heap sort),希尔排序(shell sort).如果回顾一下这些排序算法,仔细思考一下他们有什么共同点的话,你或许会发现他们都是通过比较一对对元素来实现排序的,这就是所谓的比较模型.这篇博客就将对比较模型下的常见