存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。本实验的目的是通过请求页式管理中页面置换算法模拟设计,了解虚拟存储技术的特点,掌握请求页式存储管理的页面置换算法。因为源码中我对一些关键步骤的注释已经比较清晰了,所以在本文中不会再对每一个细节都进行分析,只分析整体的代码结构和所使用到的设计模式。
博客内所有文章均为 原创,所有示意图均为 原创,若转载请附原文链接。
(1)通过计算不同算法的命中率比较算法的优劣。同时也考虑了用户内存容量对命中率的影响(命中率 = 1 - 页面失效次数 ÷ 页地址流长度)。页面失效次数为每次访问相应指令时,该指令所对应的页不在内存中的次数。在本实验中,假定页面大小为1k,用户虚存容量为32k,用户内存容量为4页到32页。
(2)produce_addstream 通过随机数产生一个指令序列,共320条指令。
A、指令的地址按下述原则生成:
1)50%的指令是顺序执行的
2)25%的指令是均匀分布在前地址部分
3)25%的指令是均匀分布在后地址部分
B、具体的实施方法是:
1)在[0,319]的指令地址之间随机选取一起点m;
2)顺序执行一条指令,即执行地址为m+1的指令;
3)在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m’;
4)顺序执行一条指令,地址为m’+1的指令
5)在后地址[m’+2,319]中随机选取一条指令并执行;
6)重复上述步骤1)~5),直到执行320次指令
C、将指令序列变换称为页地址流
在用户虚存中,按每k存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为:
第0条~第9条指令为第0页(对应虚存地址为[0,9]);
第10条~第19条指令为第1页(对应虚存地址为[10,19]);
。。。。。。
第310条~第319条指令为第31页(对应虚存地址为[310,319]);
按以上方式,用户指令可组成32页。
(3)计算并输出下述算法在不同内存容量下的命中率。
1)最佳置换算法(OPT);
2)先进先出算法(FIFO);
3)最近最久未使用页面置换(LRU);
4)最少使用页面淘汰算法(LFU)
该实验中需要实现四种页面置换算法,分别为 OPT、FIFO、LRU和LFU,通过分析发现这四种算法中都存在一种优先级的关系,只不过优先级的属性不同,比如在LRU算法中,优先级为最近一次访问距离现在的时间,即页面最近一次访问距离现在的时间越短其优先级越高,而对于LFU算法,优先级页面被访问的次数,即用户内存中页面被访问的次数越多,其优先级也就越高,越不容易被淘汰,而FIFO算法中的优先级就是进入用户空间的时间,时间越短优先级越高,OPT中的优先级就是页面下次被访问据当前的时间,时间越短优先级越高。
当我们找到了这几种算法中的优先级关系后,通过分析可以发现优先级队列可以很好的满足这个需求,而对于LRU算法的实现其实最简单的方法是如果被访问的页面存在于用户空间中,那么就将其提升至队尾(队尾入页面,队头出页面),但是为了保证多个算法的统一性,还是决定采用相同的抽象算法来实现。
而对于整体的代码结构设计,可以采用 模板方法设计模式 + 策略设计模式 的方法来进行实现,增强算法整体的灵活性、可扩展性和可维护性。
首先是Page结构体,该结构体保存了 OPT、LRU 和 LFU 算法所需的各项属性。
struct Page
{
Page() : elapsed_time_since_last_visit(0), used_count(0) {}
int virtual_memory_index;
// OPT
TimeSlice be_used_again_time_;
// LRU
TimeSlice elapsed_time_since_last_visit;
// LFU
int used_count;
};
其次是 UserMemorySpace 类,该类是用户内存空间的抽象,提供了一个模板参数 PriorityCmp,用来指定优先级队列中的优先级关系定义,并且在它的属性中存在 page_index_array_ 数组和 memory_queue_优先级队列,其中前者是保存所有的虚拟页面信息,PageInfo 中持有 Page 的引用,同时记录该页面是否存在于用户空间优先级队列中,而后者的 memory_queue_ 就是物理用户内存空间,前者的存在主要是了便于对物理用户内存空间内页面的查找定位,而该类的功能总结起来就是提供了对物理用户内存空间的基础操作(确定一个页面是否存在于用户空间中,获取一个页面,插入一个页面等等)。
template<typename PriorityCmp>
class UserMemorySpace
{
public:
struct PageInfo
{
bool in_user_memory = false;
PagePtr page = new Page();
};
typedef PageInfo * PageInfoPtr;
UserMemorySpace(int user_memory_size, int virtual_memory_size)
: user_memory_size_(user_memory_size)
, virtual_memory_size_(virtual_memory_size)
, memory_queue_(new std::priority_queue<PagePtr, std::vector<PagePtr>, PriorityCmp>())
, page_index_array_(new std::vector<PageInfoPtr>(virtual_memory_size));
inline void initPageIndexArray();
void push(int virtual_page_index);
PagePtr pop();
inline PageInfoPtr getPageInfo(int virtual_page_index);
inline PagePtr getPage(int virtual_page_index);
inline bool pageInUserMemory(int virtual_page_index);
void freshPageOrder();
std::vector<PagePtr> * getPagesInUserMemory();
private:
int user_memory_size_;
int virtual_memory_size_;
// help find page in memory queue
std::vector<PageInfoPtr> * page_index_array_;
std::priority_queue<PagePtr, std::vector<PagePtr>, PriorityCmp> * memory_queue_;
};
接下来是页面置换算法,这里采用了模板方法设计模式,其中PageReplacementAlgorithm为所有是算法的抽象基类,不提供具体实现,只规定算法所必须实现的方法,且其持有一个UserMemorySpace的引用。
// 页面置换算法基类
class PageReplacementAlgorithm;
// 最佳页面置换算法
class OPTPageReplacementAlgorithm : public PageReplacementAlgorithm;
// 先来先服务页面置换算法
class FIFOPageReplacementAlgorithm : public PageReplacementAlgorithm;
// LRU 页面置换算法
class LRUPageReplacementAlgorithm : public PageReplacementAlgorithm;
// LFU 页面置换算法
class LFUPageReplacementAlgorithm : public PageReplacementAlgorithm;
最后两个模块类,PageReplacement是对页面置换逻辑的封装,主要用于聚合PageReplacementAlgorithm和UserMemorySpace,并设置相应的物理用户内存空间的大小,而PageReplacementWrapper是对PageReplacement的进一步封装,指引用户输入相应的数据(具体实现请见源码)。
template<typename PageReplacementAlgorithm, typename PriorityCmp>
class PageReplacement;
class PageReplacementWrapper;
#include
#include
#include
#include
#include
#include
#include
#include
class Util
{
public:
static inline int getRandom(const int min_val, const int max_val, int match)
{
srand(time(0) + match);
return ((max_val - min_val - 1) == 0) ? min_val : rand() % (max_val - min_val - 1) + min_val;
}
};
typedef int TimeSlice;
int page_replacement_count = 0;
std::vector<double> * ratio = new std::vector<double>(28, 0);
struct Page
{
Page() : elapsed_time_since_last_visit(0), used_count(0) {}
int virtual_memory_index;
// OPT
TimeSlice be_used_again_time_;
// LRU
TimeSlice elapsed_time_since_last_visit;
// LFU
int used_count;
};
typedef Page * PagePtr;
template<typename PriorityCmp>
class UserMemorySpace
{
public:
struct PageInfo
{
bool in_user_memory = false;
PagePtr page = new Page();
};
typedef PageInfo * PageInfoPtr;
UserMemorySpace(int user_memory_size, int virtual_memory_size)
: user_memory_size_(user_memory_size)
, virtual_memory_size_(virtual_memory_size)
, memory_queue_(new std::priority_queue<PagePtr, std::vector<PagePtr>, PriorityCmp>())
, page_index_array_(new std::vector<PageInfoPtr>(virtual_memory_size))
{
initPageIndexArray();
}
inline void initPageIndexArray()
{
for (int i = 0; i < virtual_memory_size_; ++i)
{
(*page_index_array_)[i] = new PageInfo();
getPage(i)->virtual_memory_index = i;
}
}
void push(int virtual_page_index)
{
// if page already in user memory
PageInfoPtr pip = getPageInfo(virtual_page_index);
if (pip->in_user_memory)
{
//std::cout << "virtual page " << virtual_page_index << " already in user memory." << std::endl;
return;
}
page_replacement_count++;
if (memory_queue_->size() >= user_memory_size_) pop();
PagePtr page = getPage(virtual_page_index);
memory_queue_->push(page);
pip->in_user_memory = true;
//std::cout << "virtual page " << virtual_page_index << " swap in." << std::endl;
}
PagePtr pop()
{
PagePtr page = memory_queue_->top();
memory_queue_->pop();
getPageInfo(page->virtual_memory_index)->in_user_memory = false;
//std::cout << "virtual page " << page->virtual_memory_index << " swap out." << std::endl;
return page;
}
inline PageInfoPtr getPageInfo(int virtual_page_index)
{
return (*page_index_array_)[virtual_page_index];
}
inline PagePtr getPage(int virtual_page_index)
{
return getPageInfo(virtual_page_index)->page;
}
inline bool pageInUserMemory(int virtual_page_index)
{
return getPageInfo(virtual_page_index)->in_user_memory;
}
void freshPageOrder()
{
std::vector<PagePtr> * pages = new std::vector<PagePtr>();
while (!memory_queue_->empty())
{
// while priority equal,order by enqueue order
pages->push_back(memory_queue_->top());
memory_queue_->pop();
}
for (PagePtr page : *pages) memory_queue_->push(page);
delete pages;
}
std::vector<PagePtr> * getPagesInUserMemory()
{
std::vector<PagePtr> * pages = new std::vector<PagePtr>();
for (PageInfoPtr pi : *page_index_array_)
if (pi->in_user_memory) pages->push_back(pi->page);
return pages;
}
private:
int user_memory_size_;
int virtual_memory_size_;
// help find page in memory queue
std::vector<PageInfoPtr> * page_index_array_;
std::priority_queue<PagePtr, std::vector<PagePtr>, PriorityCmp> * memory_queue_;
};
class PageReplacementAlgorithm
{
public:
virtual void setPageAddressStream(std::vector<int> * page_address_stream) {};
virtual void setCurrentPageIndexOfPageAddressStream(int index) {};
virtual PagePtr getPage(int virtual_memory_index) = 0;
};
class OPTPageReplacementAlgorithm : public PageReplacementAlgorithm
{
public:
struct PriorityCmp
{
bool operator()(PagePtr p1, PagePtr p2)
{
return p1->be_used_again_time_ < p2->be_used_again_time_;
}
};
OPTPageReplacementAlgorithm(UserMemorySpace<PriorityCmp> * user_memory_space)
: user_memory_space_(user_memory_space)
{}
void setPageAddressStream(std::vector<int> * page_address_stream)
{
page_address_stream_ = page_address_stream;
}
void setCurrentPageIndexOfPageAddressStream(int index)
{
current_page_index_of_page_address_stream_ = index;
}
PagePtr getPage(int virtual_memory_index)
{
PagePtr page = user_memory_space_->getPage(virtual_memory_index);
updatePageBeAgainUsedTime();
user_memory_space_->freshPageOrder();
user_memory_space_->push(virtual_memory_index);
current_page_index_of_page_address_stream_++;
return page;
}
void updatePageBeAgainUsedTime()
{
std::vector<PagePtr> * pages = user_memory_space_->getPagesInUserMemory();
for (PagePtr p : *pages)
p->be_used_again_time_ = getPageNextUsedTimeFromPageAddressStream(p->virtual_memory_index)
- current_page_index_of_page_address_stream_;
}
int getPageNextUsedTimeFromPageAddressStream(int virtual_memory_index)
{
for (int i = current_page_index_of_page_address_stream_ + 1; i < page_address_stream_->size(); ++i)
if ((*page_address_stream_)[i] == virtual_memory_index) return i;
return page_address_stream_->size();
}
private:
int current_page_index_of_page_address_stream_ = 0;
std::vector<int> * page_address_stream_;
UserMemorySpace<PriorityCmp> * user_memory_space_;
};
class FIFOPageReplacementAlgorithm : public PageReplacementAlgorithm
{
public:
struct PriorityCmp
{
bool operator()(PagePtr p1, PagePtr p2)
{
return false;
}
};
FIFOPageReplacementAlgorithm(UserMemorySpace<PriorityCmp> * user_memory_space)
: user_memory_space_(user_memory_space)
{}
PagePtr getPage(int virtual_memory_index)
{
PagePtr page = user_memory_space_->getPage(virtual_memory_index);
user_memory_space_->push(virtual_memory_index);
return page;
}
private:
UserMemorySpace<PriorityCmp> * user_memory_space_;
};
class LRUPageReplacementAlgorithm : public PageReplacementAlgorithm
{
public:
struct PriorityCmp
{
bool operator()(PagePtr p1, PagePtr p2)
{
return p1->elapsed_time_since_last_visit < p2->elapsed_time_since_last_visit;
}
};
LRUPageReplacementAlgorithm(UserMemorySpace<PriorityCmp> * user_memory_space)
: user_memory_space_(user_memory_space)
{}
PagePtr getPage(int virtual_memory_index)
{
PagePtr page = user_memory_space_->getPage(virtual_memory_index);
// this page may already in user memory,avoid repeat elapsed_time_since_last_visit++
// there should judge this page whether in user memory
page->elapsed_time_since_last_visit = user_memory_space_->pageInUserMemory(virtual_memory_index) ? -1 : 0;
std::vector<PagePtr> * pages = user_memory_space_->getPagesInUserMemory();
for (PagePtr p : *pages)
p->elapsed_time_since_last_visit++;
user_memory_space_->freshPageOrder();
user_memory_space_->push(virtual_memory_index);
return page;
}
private:
UserMemorySpace<PriorityCmp> * user_memory_space_;
};
class LFUPageReplacementAlgorithm : public PageReplacementAlgorithm
{
public:
struct PriorityCmp
{
bool operator()(PagePtr p1, PagePtr p2)
{
return p1->used_count > p2->used_count;
}
};
LFUPageReplacementAlgorithm(UserMemorySpace<PriorityCmp> * user_memory_space)
: user_memory_space_(user_memory_space)
{}
PagePtr getPage(int virtual_memory_index)
{
PagePtr page = user_memory_space_->getPage(virtual_memory_index);
if (user_memory_space_->pageInUserMemory(virtual_memory_index)) page->used_count++;
else page->used_count = 1;
user_memory_space_->freshPageOrder();
user_memory_space_->push(virtual_memory_index);
return page;
}
private:
UserMemorySpace<PriorityCmp> * user_memory_space_;
};
template<typename PageReplacementAlgorithm, typename PriorityCmp>
class PageReplacement
{
public:
PageReplacement<PageReplacementAlgorithm, PriorityCmp>(){}
bool doPageReplacement()
{
for (int user_mem_space = 4; user_mem_space <= 32; ++user_mem_space)
{
page_replacement_count = 0;
user_memory_space_ = new UserMemorySpace<PriorityCmp>(user_mem_space, 32);
page_replacement_algorithm_ = new PageReplacementAlgorithm(user_memory_space_);
page_replacement_algorithm_->setPageAddressStream(page_address_stream_);
for (int index = 0; index < page_address_stream_->size(); ++index)
{
page_replacement_algorithm_->setCurrentPageIndexOfPageAddressStream(index);
page_replacement_algorithm_->getPage((*page_address_stream_)[index]);
current_page_index_of_page_address_stream_ = index;
}
double cache_hit_ratio = 1 - page_replacement_count/(page_address_stream_->size() / 1.0);
//std::cout << "user memory space is " << user_mem_space << "k, cache hit ratio " << cache_hit_ratio << std::endl;
//std::cout << cache_hit_ratio << ",";
(*ratio)[user_mem_space - 4] = cache_hit_ratio;
}
return true;
}
void setPageAddressStream(std::vector<int> * page_address_stream)
{
page_address_stream_ = page_address_stream;
}
private:
int current_page_index_of_page_address_stream_ = 0;
std::vector<int> * page_address_stream_;
UserMemorySpace<PriorityCmp> * user_memory_space_;
PageReplacementAlgorithm * page_replacement_algorithm_;
};
class PageReplacementWrapper
{
public:
PageReplacementWrapper()
{
opt_page_replacement_ = new PageReplacement<OPTPageReplacementAlgorithm, OPTPageReplacementAlgorithm::PriorityCmp>();
fifo_page_replacement_ = new PageReplacement<FIFOPageReplacementAlgorithm, FIFOPageReplacementAlgorithm::PriorityCmp>();
lru_page_replacement_ = new PageReplacement<LRUPageReplacementAlgorithm, LRUPageReplacementAlgorithm::PriorityCmp>();
lfu_page_replacement_ = new PageReplacement<LFUPageReplacementAlgorithm, LFUPageReplacementAlgorithm::PriorityCmp>();
}
bool doPageReplacement()
{
produceAddstream(320);
//produceTestAddstream();
choosePageReplacementAlgorithm();
return true;
}
bool doTest()
{
std::vector<double> * opt = new std::vector<double>(28);
std::vector<double> * fifo = new std::vector<double>(28);
std::vector<double> * lru = new std::vector<double>(28);
std::vector<double> * lfu = new std::vector<double>(28);
for (int i = 0; i < 100; ++i)
{
produceAddstream(320);
opt_page_replacement_->setPageAddressStream(page_address_stream_);
opt_page_replacement_->doPageReplacement();
for (int i = 0; i < opt->size(); ++i)
(*opt)[i] += (*ratio)[i];
ratio->clear();
fifo_page_replacement_->setPageAddressStream(page_address_stream_);
fifo_page_replacement_->doPageReplacement();
for (int i = 0; i < fifo->size(); ++i)
(*fifo)[i] += (*ratio)[i];
ratio->clear();
lru_page_replacement_->setPageAddressStream(page_address_stream_);
lru_page_replacement_->doPageReplacement();
for (int i = 0; i < lru->size(); ++i)
(*lru)[i] += (*ratio)[i];
ratio->clear();
lfu_page_replacement_->setPageAddressStream(page_address_stream_);
lfu_page_replacement_->doPageReplacement();
for (int i = 0; i < lfu->size(); ++i)
(*lfu)[i] += (*ratio)[i];
ratio->clear();
}
for (double d : *opt)
std::cout << (d / 100) << ",";
std::cout << std::endl;
for (double d : *fifo)
std::cout << (d / 100) << ",";
std::cout << std::endl;
for (double d : *lru)
std::cout << (d / 100) << ",";
std::cout << std::endl;
for (double d : *lfu)
std::cout << (d / 100) << ",";
std::cout << std::endl;
}
void choosePageReplacementAlgorithm()
{
std::cout << "There are algorithms in the program" << std::endl;
std::cout << "1.Optimization algorithm" << std::endl;
std::cout << "2.First in first out algorithm" << std::endl;
std::cout << "3.Least recently used algorithm" << std::endl;
std::cout << "4.Least frequently used algorithm" << std::endl;
std::cout << "Select an algorithm number, please." << std::endl;
int pra = 1;
bool continue_do = false;
do {
do {
std::cin >> pra;
switch (pra)
{
case 1:
std::cout << "current is Optimization algorithm" << std::endl;
opt_page_replacement_->setPageAddressStream(page_address_stream_);
opt_page_replacement_->doPageReplacement();
break;
case 2:
std::cout << "current is First in first out algorithm" << std::endl;
fifo_page_replacement_->setPageAddressStream(page_address_stream_);
fifo_page_replacement_->doPageReplacement();
break;
case 3:
std::cout << "current is Least recently used algorithm" << std::endl;
lru_page_replacement_->setPageAddressStream(page_address_stream_);
lru_page_replacement_->doPageReplacement();
break;
case 4:
std::cout << "current is Least frequently used algorithm" << std::endl;
lfu_page_replacement_->setPageAddressStream(page_address_stream_);
lfu_page_replacement_->doPageReplacement();
break;
default:
std::cout << "there is not the algorithm in the program" << std::endl;
break;
}
} while (pra < 1 || pra > 4);
std::cout << "do you try again with another algorithm(y/n)" << std::endl;
char ch = 'n';
std::cin >> ch;
continue_do = (ch == 'y');
} while (continue_do);
}
void produceAddstream(int addrNum)
{
std::cout << "Start memory management." << std::endl;
std::cout << "Producing address flow, wait for while, please." << std::endl;
instruction_sequence_ = new std::vector<int>();
while (instruction_sequence_->size() != addrNum)
{
int m = Util::getRandom(0, addrNum - 1, instruction_sequence_->size());
instruction_sequence_->push_back(m + 1);
if (instruction_sequence_->size() == addrNum) break;
int m_ = Util::getRandom(0, m + 1, instruction_sequence_->size());
instruction_sequence_->push_back(m_);
instruction_sequence_->push_back(m_ + 1);
if (instruction_sequence_->size() == addrNum) break;
int n = Util::getRandom(m_ + 2, addrNum - 1, instruction_sequence_->size());
instruction_sequence_->push_back(n);
}
page_address_stream_ = new std::vector<int>();
for (int ins : *instruction_sequence_)
{
page_address_stream_->push_back(ins / 10);
// std::cout << ins/10 << " ";
}
}
void produceTestAddstream()
{
page_address_stream_ = new std::vector<int>();
// {7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1}
page_address_stream_->push_back(7);
page_address_stream_->push_back(0);
page_address_stream_->push_back(1);
page_address_stream_->push_back(2);
page_address_stream_->push_back(0);
page_address_stream_->push_back(3);
page_address_stream_->push_back(0);
page_address_stream_->push_back(4);
page_address_stream_->push_back(2);
page_address_stream_->push_back(3);
page_address_stream_->push_back(0);
page_address_stream_->push_back(3);
page_address_stream_->push_back(2);
page_address_stream_->push_back(1);
page_address_stream_->push_back(2);
page_address_stream_->push_back(0);
page_address_stream_->push_back(1);
page_address_stream_->push_back(7);
page_address_stream_->push_back(0);
page_address_stream_->push_back(1);
}
private:
std::vector<int> * instruction_sequence_;
std::vector<int> * page_address_stream_;
PageReplacement<OPTPageReplacementAlgorithm, OPTPageReplacementAlgorithm::PriorityCmp> * opt_page_replacement_;
PageReplacement<FIFOPageReplacementAlgorithm, FIFOPageReplacementAlgorithm::PriorityCmp> * fifo_page_replacement_;
PageReplacement<LRUPageReplacementAlgorithm, LRUPageReplacementAlgorithm::PriorityCmp> * lru_page_replacement_;
PageReplacement<LFUPageReplacementAlgorithm, LFUPageReplacementAlgorithm::PriorityCmp> * lfu_page_replacement_;
};
上图为循环执行 100 次时各算法的命中率可视化,其中 x 轴为用户内存大小,y 轴为缓存命中率。这个问题可以通过上述的可视化视图来进行分析,通过上面的运行结果的可视化视图我们可以看到在循环运行100次后,FIFO的平均缓存命中率高于LRU算法,这有点违背我们以往的知识,即LRU是对FIFO的优化算法,缓存命中率应当高于FIFO。通过分析后,我认为主要是由于该实验中的随机指令生成算法造成的。
我们可以发现该随机算法虽然是保证了25%的指令位于前部,25%的指令位于后部,50%的指令是连续的,但是我们可以发现该生成算法五步为一轮,每一轮在第一步都会重新确定起点,这导致虽然整体中又50%的指令是连续的,但都是仅有两条是连续的。
而我们平时所述得LRU算法优于FIFO算法,主要是因为在实际工程中,指令的执行大多是顺序执行的,存在局部性原理,在这里可能刚刚被访问到的指令很快又会被访问到,所以此时LRU算法是最高效的。在后续我也测试了增加指令的重复性和连续性,LRU的算法命中率上升。
通过 7.1 中的图可以看到,当用户内存容量越大时,命中率是越高的,因为用户内存可常驻的物理内存越来越多,缺页次数减少,当用户物理内存和虚拟内存相等时,每个物理页面只需调入一次用户内存,即可一直常驻用户内存。
如果本文描述的内容或使用的代码存在任何问题,请及时联系我或在本篇文章的下面进行评论,我会本着对每一位学技术同学的负责态度立即修改。在后续还会有三篇计算机操作系统的算法 C++ 复现博文,如果感兴趣可以关注我。