gem5: 实现最后一级缓存LLC分区

问题:如何实现cpu中最后一级缓存分区呢?如对于LLC 2MB,cache line 64Byte, 32-way,将其分区为16路相连并保持1024 cache sets不变呢?

比如下面的4个set, 8路相连,分区后变成4路有效(标志为1的)的cache。

1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0


思路:

  • 分区的关键部分包含lru.cc和CacheSet.hh;
  • lru.cc中关于访问accessBlock()和替换findVictim()的部分需要修改;
  • accessBlock()控制每次针对LLC的访问仅访问分区内的内容(标志为1的那部分);
  • findVictim()用于控制LLC发生替换时,替换分区内最后一个块;
  • 增加一个option用于指定分区,如–part=16选项;


方法:

1.修改configs/common/Options.py,增加 –part选项;

2.将分区选项作为缓存的一部分,类似缓存的assoc参数,修改src/mem/cache/BaseCache.py,增加 part = Param.Int("PartitionSize")

3.使part在缓存配置中生效,修改configs/common/CacheConfig.py关于一级,二级和三级缓存的设置,如下:

# 三级缓存定义中增加part=options.part,指定LLC分区大小
if options.l3cache:
        system.l3 =      l3_cache_class(clk_domain=system.cpu_clk_domain,
                                   size=options.l3_size,
                                   part=options.part,
                                   assoc=options.l3_assoc)

# 修改一级缓存的part参数,因为只分区LLC,故part等于l1i_assoc不变
if options.caches:
            icache = icache_class(size=options.l1i_size,
                                  part=options.l1i_assoc,
                                  assoc=options.l1i_assoc)
            dcache = dcache_class(size=options.l1d_size,
                                  part=options.l1d_assoc,
                                  assoc=options.l1d_assoc)

# 修改二级缓存的part参数,因为只分区LLC,故part等于l2_assoc不变
if options.l2cache:
            system.cpu[i].l2 = l2_cache_class(clk_domain=system.cpu_clk_domain,
                                          size=options.l2_size,
                                          part=options.l2_assoc,
                                          assoc=options.l2_assoc)

4.为了使part分区对缓存的访问行为产生影响,故需要修改换成替换策略。首先在src/mem/cache/tags/lru.hh中定义part,

//在assoc下面加入part定义,必须按照这个顺序,与src/mem/cache/BaseCache.py中的定义顺序需要一致。
 protected:
    /** The associativity of the cache. */
    const unsigned assoc;
    /** The partition size. by sff */
    const unsigned part;
    /** The number of sets in the cache. */
    const unsigned numSets;

其次,修改src/mem/cache/tags/lru.cc,初始化part参数。

//增加 part(p->part)的初始化
LRU::LRU(const Params *p)
    :BaseTags(p), assoc(p->assoc), part(p->part),
     numSets(p->size / (p->block_size * p->assoc))

再次,修改src/mem/cache/tags/Tags.py中关于LRU类中定义参数part,因为Params *p引用的时候需要该设置。

class LRU(BaseTags):
    type = ‘LRU‘
    cxx_class = ‘LRU‘
    cxx_header = "mem/cache/tags/lru.hh"
    assoc = Param.Int(Parent.assoc, "associativity")
    part = Param.Int(Parent.part, "partition")

通过测试,发现cacheset中不能识别part参数,需要修改src/mem/cache/tags/cacheset.hh,增加其定义:

template <class Blktype>
class CacheSet
{
  public:
    /** The associativity of this set. */
    int assoc;
     //增加部分
    /** The partition size. */
    int part;

通过测试,发现cacheset中识别的part参数有误,是由于src/mem/cache/tags/lru.cc中未给set[i]赋值。

//在lru的初始化中添加sets[i].part = part;
unsigned blkIndex = 0;       // index into blks array
    for (unsigned i = 0; i < numSets; ++i) {
        sets[i].assoc = assoc;
        sets[i].part = part;

测试方法备注:

使用下面的调试方法测试,注意l1, l2和l3的相连度都不同,方便调试的时候区分。

# 修改源代码src中内容后,运行前一定要编译一次,否则修改代码不会生效。
scons -j 8 build/ALPHA/gem5.debug

# 开始调试
gdb --args  build/ALPHA/gem5.debug configs/example/spec06_l3_se.py --benchmark=bzip2 -n 1 --cpu-type=detailed --cpu-clock=2GHz --caches --l1i_size=32kB --l1i_assoc=4 --l1d_size=32kB --l1d_assoc=8 --l2cache --l2_size=256kB --l2_assoc=16 --l3cache --l3_size=2MB --l3_assoc=32 --part=9

#设置断点
b lru.cc:80  #即LRU初始化代码中
b cacheset.hh:100 #即 way_id = i;中测试part

#运行到lru.cc:80时,通过下面的方式鉴别是否设置正确,cacheset也一样
p assoc
p part
p name()

5.修改分区的访问控制,访问会调用src/mem/cache/tags/cacheset.hh中的findBlk函数,修改它即可。对于LLC的访问将限定在part内。

template <class Blktype>
Blktype*
CacheSet<Blktype>::findBlk(Addr tag, int& way_id) const
{
    /**
     * Way_id returns the id of the way that matches the block
     * If no block is found way_id is set to assoc.
     */
    /**
     * this is the source code.
    way_id = assoc;
    for (int i = 0; i < assoc; ++i) {
        if (blks[i]->tag == tag && blks[i]->isValid()) {
            way_id = i;
            return blks[i];
        }
    }
    */
    // modifed for LLC partition. L1 and L2 access normal.
    way_id = part;
    for (int i = 0; i < part; ++i) {
        if (blks[i]->tag == tag && blks[i]->isValid()) {
            way_id = i;
            return blks[i];
        }
    }
    return NULL;
}

6.修改分区的替换方式。src/mem/cache/tags/lru.cc中的findVictim函数。在part分区最后替换。

LRU::BlkType*
LRU::findVictim(Addr addr, PacketList &writebacks)
{
    unsigned set = extractSet(addr);
    // grab a replacement candidate
    //BlkType *blk = sets[set].blks[assoc-1];  this is the source code.
    BlkType *blk = sets[set].blks[part-1]; // modified

    if (blk->isValid()) {

        DPRINTF(CacheRepl, "set %x: selecting blk %x for replacement, blk refCount: %d, drd: %d \n",
                set, regenerateBlkAddr(blk->tag, set), blk->refCount, blk->drd);
    }
    return blk;
}

7.重复上述备注中的测试,Done! 查看part及part分区后的数据是否如预期一样。分区修改完毕。



注意:

需要在三级缓存配置的se.py中添加如下内容,以免以后使用se.py时没有设置part参数而导致LLC自动分区了。

# default LLC(L3) partition 16, we should specify this args. Otherwise when we do not want partition, it still executes. by sff
if ‘--part‘ not in sys.argv:
    print "Error: default LLC(L3) partition is 16, we should specify this args ‘--part=PART‘. Otherwise when we do not want partition, it still executes."
    sys.exit(1)

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-06 14:58:24

gem5: 实现最后一级缓存LLC分区的相关文章

hibernate 一级缓存

一级缓存 为什么要用缓存? 目的:减少对数据库的访问次数!从而提升hibernate的执行效率! Hibernate中缓存分类: 一级缓存 二级缓存 概念 1)Hibenate中一级缓存,也叫做session的缓存,它可以在session范围内减少数据库的访问次数!  只在session范围有效! Session关闭,一级缓存失效! 2)当调用session的save/saveOrUpdate/get/load/list/iterator方法的时候,都会把对象放入session的缓存中. 3)S

[转] Hibernate一级缓存、二级缓存

缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据. 缓存的介质一般是内存,所以读写速度很快.但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质.缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期. hibernate的缓存包括Session的缓存和SessionFactory的缓

26Mybatis_一级缓存及其测试

这篇文章讲解一级缓存: 先介绍一级缓存的原理: 1.我们先不看虚线部分: 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息. 得到用户信息,将用户信息存储到一级缓存中. 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息. 2.我们带上虚线部分一起看: 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息. 得到用户信息,将用

hibernate一级缓存和二级缓存的区别

缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据. 缓存的介质一般是内存,所以读写速度很快.但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质.缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期. Hibernate的缓存包括Session的缓存和SessionFactory的缓

hibernate 一级缓存和二级缓存

hibernate一级缓存:session缓存即事务级缓存,session关闭,缓存自动销毁,开发人员不用管理,由hibernate管理,save.update.saveoOrUpdate.lock.load.list会自动向一级缓存中存放数据,get,load,list会自动从一级缓存中取数据,可调用evict(Object object)和clear()清除缓存. hibernate二级缓存:sessionFactory缓存即进程级别缓存,由缓存插件实现,如OSCache,对hibernat

Hibernate的一级缓存

Hibernate的一级缓存其实就是Session内置的一个Map,用来缓存它操作过的实体对象,对象的主关键字ID是Map的key,实体对 象就是对应的值.所以,一级缓存是以实体对象为单位进行存储的,访问时也是以实体为单位的(直接访问属性是不能使用缓存的),并且要求使用主关键字ID来 进行访问. 一级缓存是由Session提供的,所以它只存在于Session的生命周期中,当程序调用 save(),update(),saveorupdate()等方法以及调用查询接口list,filter,iter

Hibernate的session一级缓存

一级缓存是Session周期的,当session创建的时候就有,当session结束的时候,缓存被清空 当缓存存在的时候,每次查询的数据,都会放在缓存中,如果再次查询相同的数据,则不会再次查询数据库,可以有效的减少数据库的访问量. 但是,session的生命周期很短,当session创建,进行数据库操作后,就会被关闭,同样的,缓存就会被清空.如果是在javaweb中,session的生命周期,就是浏览器向服务器的一次请求.所以,后面hibernate会有相应的二级缓存 操作缓存的几个方法介绍 一

Hibernate一级缓存和二级缓存深度比较

1.什么是缓存 缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据. 缓存的介质一般是内存,所以读写速度很快.但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质.缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期. Hibernate的一级缓存是内置的,不能被卸载. Hiberna

Hibernate学习笔记(三) — Hibernate 的一级缓存意义

什么是缓存? 缓存说白了,就是应用程序向数据库要数据,然后把一些数据,临时的放在了内存的区域中,第二次再要数据的时候,直接从内存中拿即可. 缓存需要解决的事情: 1.能把数据放入缓存 2.能把数据从缓存中取出来 3.如果缓存中的数据发生变化,需要把数据同步到数据库中 4.把数据库中的数据同步到缓存中 5.hits命中率低的对象应该及时从缓存中移走 分布式缓存: 为什么会有分布式缓存? 应用程序运行在服务器上,并发访问时,服务器压力过大,分布式缓存就是来分担服务器压力的. 分布式缓存之间的数据是同