2019 CMU 15-445 Project 1 Buffer Pool笔记与思路

题目要求

这次学15-445,就是填之前不好好学习的坑,都是泪。把lab的思路记录下来方便以后查阅,也是跟大家交流思想。言归正传,直接进入主题。

这次题目要求实现一个Buffer Pool,作用是为了减少磁盘访问次数。其中,Task 1 完成Clock Replacer替换算法,Task 2利用1来完成buffer pool

整体思路

Task 1: Clock Replacer

Clock Replacer置换算法

先说说Clock Replacer算法。思想很简单,就像钟表的指针一样一圈一圈遍历,直到找到符合替换的位置。文字描述过于复杂,直接利用图说明。

先做出如下假设,buffer的大小是3,访问的序列是1,2,3,1,4,3。

最初的三个元素1,2,3会直接加入到buffer中,如下表所示

numref_bitclock_hand
11<-
21
31

当访问到第二个1时,因为1在buffer中,直接读取。下面访问4,这时发现buffer中并没有4,那么就需要找一个位置替换并存入4。首先会将1处的ref_bit置0,并且指针下移一位,如下表。

numref_bitclock_hand
10
21<-
31

现在,clock_hand指向的2,而ref_bit仍然为1,则这一位也不能替换,那么就把对应的ref_bit置0,指针继续下移,如下表。

numref_bitclock_hand
10
20
31<-

同理,指针指向3遇到的情况与上次相同,因此继续执行相同操作。但是clock_hand到达边界,需返回到初始位置。便得到如下状态

numref_bitclock_hand
10<-
20
30

此时clock_hand指向的ref_bit为0,该位置能够被替换,替换1加入4,并把ref_bit置1,遍得到如下状态

numref_bitclock_hand
41<-
20
30

最后访问元素3,发现buffer中缓存有3,直接输出即可。

题目功能要求

题目要求完成四个函数,分别是Victim(),Pin(),Unpin(),Size()。这四个函数各有分工:

  • Victim():用来负责替换buffer pool中的元素。具体做法如下,从当前指针开始计算,如果需要读取的值在replacer中且指针指向的ref_bitTrue,就把它置为False,否则,就替换当前frame
  • Size():返回当前replacer中能被替换frame的数量。

Pin()Unpin()这两个函数单独拿出来仔细阐述一下,因为实在是困扰了我很久(真是技不如人,看得我云里雾里)。

实质上,执行Pin()Unpin()的都是buffer pool,如果buffer poolpage给pin了,说明这个page有进程访问,不能将它写回磁盘。而Unpin()则是有进程结束访问page的标志。执行Unpin()函数后,会发生以下两种情况:

  1. 没有进程访问page
  2. 仍然有进程访问page,即pin_count不为0。

对于情况1,该frame就可以通过调用Unpin()添加到Clock Replacer中,并将ref_bit置为False。而对于情况2,存有pageframe就不能被替换。

Pin()Victim()这两个函数非常相似,很多地方容易混淆。这里要说下Size()的具体含义,他表示能被替换的frame数量,Pin()Victim()都能让size减少。具体来说,Pin()就是buffer pool将某个pagepin住。如果这个pageUnpin()后还没来及替换又Pin()了,那这个page就不能被替换,所以size减少了。而Victim()就是选择一个frame进行替换。他把能够替换的frame替换掉了,那么可替换的frame数量当然会由此减少。

还有一点需要注意,只有Victim()才能更改clock_hand指针。

Task 2: Buffer Pool

Buffer Pool缓存磁盘中的page数据,其中每一个数据块叫做frame,如果frame被进程修改了,则称之为dirty page,必须将其写回磁盘后才能替换。这里列出具体函数的实现思路以供以后查阅复习,肯定有考虑不周全的地方,请各位大佬留言指正。

NewPageImpl(page_id)

这个函数是创建一个可用的page,下面是程序要求,这里不做赘述。

    // 0.   Make sure you call DiskManager::AllocatePage!
    // 1.   If all the pages in the buffer pool are pinned, return nullptr.
    // 2.   Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
    // 3.   Update P's metadata, zero out memory and add P to the page table.
    // 4.   Set the page ID output parameter. Return a pointer to P.

在实现的过程中,我把上述要求调换了顺序,先进行1,2然后到0。直接判断是否全pin,否则从freelist中选择页面,如果freelist中没有可用的再使用Victim()找到需要替换的页。最后就是更新元数据等。

UnpinPageImpl()

如果该pagepin_countUnpin后为0才能加入到replacer中准备替换并返回true。其他如该页不存在、pin_count<=0等情况都返回false

FetchPageImpl()

该函数的参数为page_id,即buffer pool要使用本函数来寻找对应id的内容。那么如果在缓存区中国呢找到了,就直接读取且pin_count++,否则替换掉某一位置。如果buffer pool中没有能够替换的位置,则返回空指针。

剩余的函数由于没有测试用例,这里就不把它贴出来,免得今后误导自己。总之实现的方法都不难,搞清楚pageframe问题就能迎刃而解。

最后&疑问

这里还有很多不足的地方,比如没有考虑多线程。

在调试过程中发现unordered_map对一个空表查询或者删除映射,查找被删除的元素依然会返回0,这不得不让我把删除元素的 value 值设置为-1,但在其他程序中并不存在这样的问题。望各位能为我解惑。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
MVAPICH2-GDR是一种基于InfiniBand RDMA技术的高性能计算软件栈,支持GPU加速的RDMA操作。下面是一个使用MVAPICH2-GDR进行GPU RDMA操作的示例案例: 1. 安装MVAPICH2-GDR软件栈,并确保系统中已经安装了CUDA运行时和InfiniBand网络。 2. 编写一个C或C++程序,使用MVAPICH2-GDR提供的接口进行GPU RDMA操作。以下是一个简单的示例程序: ``` #include <mpi.h> #include <cuda_runtime.h> #include <cuda.h> #include <cuda_runtime_api.h> #include <gdrapi.h> #define BUF_SIZE (1 << 20) // 1MB buffer int main(int argc, char **argv) { int me, np; char *buf_h, *buf_d; int ib_dev = 0; // use the first InfiniBand device int ib_port = 1; // use the second port of the InfiniBand device int peer_rank = 1; // the rank of the peer process uint64_t peer_addr; // the GPU address of the peer buffer uint32_t rkey; // the remote key of the peer buffer gdr_t g; // the GDR context MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &me); MPI_Comm_size(MPI_COMM_WORLD, &np); if (np != 2) { if (me == 0) { fprintf(stderr, "This program requires exactly 2 processes.\n"); } MPI_Finalize(); return 1; } cudaMalloc(&buf_d, BUF_SIZE); buf_h = (char *) malloc(BUF_SIZE); // register the buffer with GDR g = gdr_open(); gdr_pin_buffer(g, buf_d, BUF_SIZE, &rkey); gdr_map(g, ib_dev, ib_port, rkey, BUF_SIZE, &peer_addr); if (me == 0) { MPI_Send(&peer_addr, sizeof(peer_addr), MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD); MPI_Send(&rkey, sizeof(rkey), MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD); MPI_Status status; MPI_Recv(buf_h, BUF_SIZE, MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD, &status); } else { MPI_Status status; MPI_Recv(&peer_addr, sizeof(peer_addr), MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD, &status); MPI_Recv(&rkey, sizeof(rkey), MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD, &status); // use GDR to access the peer buffer gdr_map(g, ib_dev, ib_port, rkey, BUF_SIZE, &peer_addr); gdr_copy_to_mapping(g, peer_addr, buf_h, BUF_SIZE); gdr_unmap(g, peer_addr, BUF_SIZE); MPI_Send(buf_h, BUF_SIZE, MPI_BYTE, peer_rank, 0, MPI_COMM_WORLD); } gdr_unpin_buffer(g, buf_d); gdr_close(g); cudaFree(buf_d); free(buf_h); MPI_Finalize(); return 0; } ``` 3. 编译并运行程序。使用MVAPICH2-GDR提供的mpicc编译器编译程序,并使用mpirun命令启动MPI进程。例如: ``` mpicc -I/path/to/mvapich2-gdr/include -L/path/to/mvapich2-gdr/lib -lgdrapi -o gpu_rdma gpu_rdma.cu mpirun -np 2 ./gpu_rdma ``` 该程序创建了两个MPI进程,每个进程都在GPU上分配了一个1MB的缓冲区。进程0向进程1发送其缓冲区的GPU地址和远程键值,并等待进程1发送其缓冲区的内容。进程1接收进程0发送的GPU地址和远程键值,并使用GDR在GPU上访问进程0的缓冲区,并将其内容发送回进程0。 注意:在实际应用中,需要根据具体的RDMA操作需求编写程序,并进行相应的优化和调试。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值