全网最详细的手把手模拟实现Cache

news2024/11/23 7:48:22

前言:本文内容较多(字数1w),不仅包含理论知识,还进行了全面实践。本文对前三章理论内容粗略解释,建议去b站观看哈工大和王道考研的操作系统中虚拟存储相关章节,然后对于设计实现就游刃有余了。

博主写这篇文章,主要是以实现CacheSim模拟器为主,目的是对CPU模拟器进行丰富,来研究cpu乱序执行机制与回滚机制带来的meltdown漏洞。

文章目录

  • 一、概述
    • 1.1 简介
    • 1.2 cache和主存的关系
    • 1.3 Cache的基本结构
    • 1.4 Cache的读操作
    • 1.5 Cache的写操作
    • 1.6 Cache的改进
  • 二、Cache——主存的地址映射
    • 2.1、直接映射
    • 2.2、全相联映射
    • 2.3、组相联映射
  • 三、替换算法
  • 四、设计
    • 4.1 设计流程
    • 4.2文件编写
      • 4.2.1Cache_line类
      • 4.2.2 变量
      • 4.2.3 函数
      • 4.2.4 常量
  • 五、详细实现
    • 5.1 程序入口
    • 5.2 初始化
    • 5.3 load_trace 加载trace文件函数
    • 5.4 check_cache_hit 检查是否cache命中
    • 5.5 命中之后的操作
    • 5.6 未命中后的操作
    • 5.7 get_cache_free_line获得当前可用的line函数
    • 5.8 set_cache_line将cache line中的数据写回到内存函数

一、概述

1.1 简介

在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。 当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。 缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。有效利用这种局部性,缓存可以达到极高的命中率。 在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。

1.2 cache和主存的关系

在这里插入图片描述

一个块包含16个字节,并且内存的编制单位是字节,那么块内地址就是4位,剩余的部分就是储存块的编号。

1.为什么不需要计算cache地址?

由于主存块和cache块大小是相同的,所以块内地址位数是完全相同的,当某一个块,在内存和cache中进行传送的时候,是以整体进行的,块内并无变化所以两者的块内地址部分,值是完全相同的。

2.cache上的标记是来标记什么的?
标记了主存块和cache块之间的对应关系。如果将主存中的块调入到cache中,就可以将主存块号写入到这个标记中,当接受来自cpu给出的访存地址,就可以确定是否在cache中,将地址中的块号和标记进行比较即可,若相等,并且该cache块是有效的,就可以直接从cache中获取信息。

3.命中率与块长大小关系?
块太小,就没有充分用到局部性原理;块太大,导致块数过少,若块中只有少部分信息是cpu
有用的,其他信息没有用到,也会影响命中率。

1.3 Cache的基本结构

在这里插入图片描述
1.地址映射(存:装入):
map,主存当中的一个块,如果要放到cache当中的话,它可以被放到cache的哪一个块或者那些块当中,这就是映射规则。
2.变换结构(取:查找):
把主存的块号给它转换成相应的cache的块号,或者把主存的地址转换成cache的地址,在cache当中找到相应的主存块进行访问。
3.替换算法

1.4 Cache的读操作

在这里插入图片描述

1.5 Cache的写操作

Cache和主存的一致性问题:

  • 写直法:
    写操作时数据既写入主存又写入cache
    写操作的时间就是访问主存的时间
    cache块退出时不需要对主存执行写操作,更新策略比较容易实现
    但对于累加求和的程序,需要反复访存
  • 写回法
    写操作时只把数据写入cache而不写入主存
    当Cache数据被替换出去时才写回主存
    但保持不了一致性(并行计算机系统)

1.6 Cache的改进

(1)增加级数
片内cache
片外cache
(2)统一缓存和分立缓存
指令cache
数据cache
(与指令执行的控制方式有关:是否流水)

二、Cache——主存的地址映射

2.1、直接映射

在这里插入图片描述
定义:主存中的某一指定块,它只能映射到或者只能装载到某一指定的Cache块当中,任何一个区它的第一块只能放到Cache存储体的字块0当中,即
在这里插入图片描述

如果cpu给出一个地址,将其分为三部分:区号 | 块号 | 块内偏移地址
区号:主存字块标记(与“标记”占相同t位数)
块号:Cache字块地址
偏移地址:字块内地址
在这里插入图片描述

由于cache里面的字块0(第零块),它可能装载的是主存当中任何一个区大的第一块,到底是哪个区的第零块呢?需要将区号写到Cache存储体的“标记”当中。即“标记”记录主存的区号
根据Cache字块地址可从Cache中找到块号,对于此块是否为要找的指定区的指定块,就需要将地址给出的区号与Cashe中现在的区号(标记)进行比较即可。

缺点:当第二次需要使用主存2的c次方的内容时,需要重新将内容代替Cache的第零块以及下面的块,因为直接映射的规则就是某一区的第一块只能放在cache第零块中。

每个缓存块 i 都可以和若干个主存块对应
但是每个主存块 j 只能和一个缓存块对应

2.2、全相联映射

在这里插入图片描述
主存当中任何一个块都可以被放入到Cache的任何一个块当中。只要Cache中有空闲块就可以将主存块调入进来,提高了Cache利用率。但是如果主存中某一块调入Cache中,那它可能在Cache的任意一个块中,所以需要将主存地址的主存块号标记与Cache中的所有块的标记进行比较(直接映射是直接从Cache中找块,然后只需要一次比较即可,即主存块号标记与Cache中该块的标记进行比较),另外比较器的长度也会长(t+c)。

在这里插入图片描述

2.3、组相联映射

折中方案:既不是随意放,也不是指定放,而是指定部分位置随意放。
在这里插入图片描述

1.先把Cache分成块,这些块再被划分成若干个组。
每组当中包含两块。

2.再将主存分成块,这些块进行划分成区,每个区的大小和Cache中组数是相同的(每个区的块数=组数)
主存储器的第零块可以放在Cache第零组的任意一个位置(全相联的特性),而区号指定对应标记(直接映射的特性)。

这就保证了同一区的块,只要满足对应组内有位置,就可以调入(全相联的特性)。

当需要查看是否已存在Cache中时,只需要给出区号和区内块号,给出块的标号(j)后,就可以找到(映射Q)给定的组(i),将给定的组当中的几个块的标记与区号进行比较即可,不需要和每一个Cache块比较(直接映射特性)。

在这里插入图片描述
在这里插入图片描述

三、替换算法

在这里插入图片描述

四、设计

4.1 设计流程

  • 1 指令读取

由于主存访问trace以文件形式给出, trace文件下载 所以需要从文件中读取访存trace。其中trace的格式如下:

s 0x1fffff50 1

每行的第一个字符是表示该条指令的类型,s为写(store),r为读(read)。中间的十六进制数为内存地址,最后一个整数是指存储器访问指令之间的间隔指令数。例如第5条指令和第10条指令为存储器访问指令,且中间没有其他存储器访问指令,则间隔指令数为4。 我们写的模拟器中暂不考虑最后这个参数的使用。

  • 2 对一条指令进行分析

在从trace文件中读取一条指令之后,对指令进行分析,对地址进行变换,查看地址里的数据是否在cache中,获得命中与否的结果。

  • 3 检查是否命中

通过对地址进行操作,检查是否命中

  • 4 查找空闲line

如果读命中,则只需要更新时间戳,如果是写操作,则标记脏位。 如果之前没有命中,则需要从当前Cache中找到可以使用的块,优先当前是invaild状态的line,否则执行替换算法,找到需要被替换的块,同时如果要替换的块中原来的数据是脏的,写回到内存中。

  • 5 将数据加载到cache line中

如果miss,通过1.4找到可以放置数据的cache line,下一步则就是将数据从主存中加载到cache line上。

4.2文件编写

4.2.1Cache_line类

Cache_Line单独做一个类,tag采用无符号32位

class Cache_Line {
public:
    _u32 tag;
    /**计数,FIFO里记录最一开始的访问时间,LRU里记录上一次访问的时间*/
    union {
        _u32 count;
        _u32 lru_count;
        _u32 fifo_count;
    };
    _u8 flag;
    _u8 *buf;
};

count用来记录访问的时间,采用union数据结构,后面在更新count的时候,只需要针对不同的替换算法,进行特殊赋值即可,而且不会导致一直使用count出现的思路混乱。这个值的变化后面讲替换算法重点说明。 buf本意是存储内存里的数据,参看上一章中结构说明的bank。 flag则是一些标记位的存储:

位数7~3210
作用保留Locking位脏位有效位

4.2.2 变量

变量意义
cache_sizecache总大小,单位byte
cache_line_size一个cache line的大小,单位byte
cache_line_numcache一共有多少个line
cache_mapping_ways几路组相联
cache_set_size整个cache有多少组(set)
cache_set_shifts在内存地址划分中占的位数,log2(cache_set_size)
cache_line_shifts在内存地址划分中占的位数,log2(cache_line_num)
caches真正的cache数组
tick_count指令计数器
cache_bufcache bank,存内存数据的地方
cache_free_num当前空闲cache line的数量
cache_r_count, cache_w_countcache读写内存总数据量
cache_hit_count, cache_miss_counthit和miss计数
swap_style替换算法

4.2.3 函数

函数功能
check_cache_hit检查是否命中
get_cache_free_line获得当前空闲的cache line,如果没有空闲的则执行替换算法,返回一个可以替换的块
set_cache_line将内存中的数据加载到通过get_cache_free_line获得的cache line上去
do_cache_op对一条指令进行分析
load_trace加载trace文件,开始进行分析
set_swap_style设置本次的缓存替换算法

4.2.4 常量

const unsigned char CACHE_FLAG_VAILD = 0x01;	//有效位
const unsigned char CACHE_FLAG_DIRTY = 0x02; //脏位
const unsigned char CACHE_FLAG_LOCK = 0x04; //Lock位
const unsigned char CACHE_FLAG_MASK = 0xff;	//初始化

对应本文中的第一个表格,lock位是留着后面做cache locking用的。后续会修改项目实现这个功能。CACHE_FLAG_MASK主要是在写入cache line时进行初始化flag用到。其他则是通过与cache line中的flag进行&操作,判断有效性或者脏位与否。

五、详细实现

5.1 程序入口

main.cpp主要针对一个测试文件,配置了不同cache line大小,不同组相联路数,不同的替换策略。默认使用写回法。默认cache大小32KB(0x8000 Bytes)。 在每次循环里,首先初始化cache的配置,然后设置替换策略,最后读入trace文件并开始模拟内存读写过程。

5.2 初始化

在类CacheSim的构造函数里,主要根据上一章一些变量的意义进行了初始化,主要还是注意shifts的两个变量,使用库函数log2()算出位数。 cache line实际是存储在caches变量中,memset填充0,则cache line的flag默认也是填充的0。 注意记得析构函数中对caches和cache_buf的释放。

5.3 load_trace 加载trace文件函数

通过fgets函数读文件到buf字符数组里,然后sscanf从buf里格式化读入一行指令,注意,原文件里是s 0x1fffff50 1的格式,最后一个参数我们先不考虑,因此只读入了指令类型(store or read)和内存地址。 通过判断style来决定是读还是写,每读取(执行)一条,我们的tick_count就++,同样用来记录是读写的计数器也相应++。 rcount/wcount是来统计读写指令的数量,而cache_r_count/cache_w_count是统计内存和cache进行数据读写的次数。在cache miss找到替换line时,如果找到的line中是脏数据,需要将其写回到内存中,此时就产生了数据通信,同样将数据从内存中加载到这个替换line中也有数据通信。 trace文件处理完成后,就是打印结果。目前主要关注的三个方面:

  • 指令计数
  • miss率/hit率
  • 读写数据通信

5.4 check_cache_hit 检查是否cache命中

do_cache_op分析一条指令

传入参数是地址和读写的标记(s or r)。对于load和store指令而言,只有两种情况:hit or miss。则中间主要涉及三个部分:检查是否命中、命中之后的操作、miss之后的操作。

int CacheSim::check_cache_hit(_u32 set_base, _u32 addr) {
    /**循环查找当前set的所有way(line),通过tag匹配,查看当前地址是否在cache中*/
    _u32 i;
    for (i = 0; i < cache_mapping_ways; ++i) {
        if ((caches[set_base + i].flag & CACHE_FLAG_VAILD) && (caches[set_base + i].tag == ((addr >> (cache_set_shifts + cache_line_shifts))))) {
            return set_base + i;
        }
    }
    return -1;
}
int CacheSim::check_cache_hit(_u32 set_base, _u32 addr) 

检查命中的传参有两个,一个是set的基址,一个是当前trace中的内存地址。

set = (addr >>cache_line_shifts) % cache_set_size;
set_base = set * cache_mapping_ways;

根据组相联中我们对地址的区域划分,来获取当前地址映射到了cache的哪一个set中去了。(line占的位数是块内偏移地址,set占的位数是组索引,所以去掉line占的位数,带着剩余的高位,即tag+set,直接取余,或者去掉tag占的高位后进行取余也许)

而cache在模拟器中是作为一维数组对待的,所以还需要获取这个set的首地址。(知道了第几组,那么首地址=组数*组的大小)

命中与否的判定则是根据tag的匹配情况,如果当前地址addr的tag和我们cache中映射set中的某一个line tag相同(caches[set_base + i].tag == ((addr >> (cache_set_shifts + cache_line_shifts)))),且这个line是有效的(caches[set_base + i].flag & CACHE_FLAG_VAILD),那么返回这个line的在这个一维数组cache的index(即第几个cache line /块号)。如果当前set中没有找到,说明这个addr中的数据并没有加载到cache上,返回-1

5.5 命中之后的操作

if (index >= 0) {
    cache_hit_count++;
    //只有在LRU的时候才更新时间戳,第一次设置时间戳是在被放入数据的时候。所以符合FIFO
    if (CACHE_SWAP_LRU == swap_style)
        caches[index].lru_count = tick_count;
    //直接默认配置为写回法,即要替换或者数据脏了的时候才写回。
    //命中了,如果是改数据,不直接写回,而是等下次,即没有命中,但是恰好匹配到了当前line的时候,这时的标记就起作用了,将数据写回内存
    if (!is_read)
        caches[index].flag |= CACHE_FLAG_DIRTY;
}

如果命中了,皆大欢喜,命中计数++,如果替换算法是LRU,则更新一下时间戳(有关时间戳的事情后面会详细说明)。 如果是一个写内存的操作,则需要把命中的这个cache line标志设置为脏,因为指令的意思是往这个内存地址写数据,而cache中原来有一份数据,由于我们的默认cache写的方式是写回法(写回法,write back,即写cache时不写入主存,而当cache数据被替换出去时才写回主存),因此这里只是先标记为脏数据,等该line要被替换出去的时候,再把该line中的数据写回到内存。

5.6 未命中后的操作

index = get_cache_free_line(set_base);
set_cache_line((_u32)index, addr);
if (is_read) {
    cache_r_count++;
} else {
    cache_w_count++;
}
cache_miss_count++;

如果miss了,先获得可用的line(5.7)(可能是当前set恰好不满,有空闲,也可能是通过替换算法获得可以替换的line),然后将addr内存地址中的数据写入到cache line里,相应的读写通信次数也要++,cache_r_count已经在5.3中的load_trace函数部分介绍过了。

5.7 get_cache_free_line获得当前可用的line函数

_u32 CacheSim::get_cache_free_line(_u32 set_base)
当然是从当前映射的set里找可用的line,传入在“一维数组”的cache里映射set的index。

for (i = 0; i < cache_mapping_ways; ++i) {
    if (!(caches[set_base + i].flag & CACHE_FLAG_VAILD)) {
        if (cache_free_num > 0)
            cache_free_num--;
        return set_base + i;
    }
}

循环当前set一遍,判断标志位中的有效位,如果有无效的cache line,那么就意味这个line是“空闲”的。直接返回它的index即可。 循环完都没有找到,说名当前的cache set是满的,那么执行替换算法:

free_index = 0;
if(CACHE_SWAP_RAND == swap_style){
    free_index = rand() % cache_mapping_ways;
}else{			//FIFO
    min_count = caches[set_base].count;
    for (j = 1; j < cache_mapping_ways; ++j) {
        if(caches[set_base + j].count < min_count ){
            min_count = caches[set_base + j].count;
            free_index = j;
        }
    }
}
if (CACHE_SWAP_LRU == swap_style)
    caches[index].lru_count = tick_count;

随机替换算法的种子在CacheSim的构造函数里进行过初始化了。 对于FIFO(First in, first out)先进先出替换算法,其cache line的时间戳记录的是这个line被填充数据时的时间,即进入“使用队列”时的时间,因此FIFO是找到最先进入这个队列的line,将其替换出去。 LRU(Least Recently Used)最近最少使用替换算法,则当line被访问的时候,就需要更新其时间戳,因此在do_cache_op函数里,当cache 命中时,需要更新其时间戳。

min_count在FIFO中获得是最早进入队列的line,而在LRU中,则是这个队列中已经很长时间没有访问的line。

如果通过替换算法获得的这个line中原有的数据是脏数据,那么标记其脏位

if (caches[free_index].flag & CACHE_FLAG_DIRTY) {
    caches[free_index].flag &= ~CACHE_FLAG_DIRTY;
    cache_w_count++;
}

5.8 set_cache_line将cache line中的数据写回到内存函数

void CacheSim::set_cache_line(_u32 index, _u32 addr) {
    Cache_Line *line = caches + index;
    // 这里每个line的buf和整个cache类的buf是重复的而且并没有填充内容。
    line->buf = cache_buf + cache_line_size * index;
    // 更新这个line的tag位
    line->tag = addr >> (cache_set_shifts + cache_line_shifts );
    line->flag = (_u8)~CACHE_FLAG_MASK;
    line->flag |= CACHE_FLAG_VAILD;
    line->count = tick_count;
}

传入的是“一维数组”cache中要写回的line的index以及要写入line的内存地址。 这里的buf并没有什么作用,只是表示将内存里的数据写入了当前这个line,而在我们的cache模拟器里,并没有真正的数据流动。 更新tag则根据第一章中地址划分的内容,对addr进行移位操作即可。 flag先清空,CACHE_FLAG_MASK = 0xff则~CACHE_FLAG_MASK = 0x00,然后将最后一位有效位置为有效,并更新时间戳。

完成后代码会放在评论区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193635.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++数论————质数筛法(单独判断一个数,判断N个数) 埃氏筛法

质数想必大家都不陌生从小学到大质数的概念&#xff1a;一个数如果除了1和本身之外没有其他的因子&#xff0c;那么这个数被称为质数今天要讲两个知识点&#xff1a;在C中如何判断一个数是否为质数在C中如何判断1-N之间哪些数为整数在C中如何判断一个数是否为质数这个知识点较为…

当湖北《汉川》遇到湖南《早安隆回》,杨语莲会是下一个袁树雄吗

古有花木兰&#xff0c;替父去从军。如今在中国华语乐坛&#xff0c;继《早安隆回》袁树雄之后&#xff0c;又出现了《汉川》杨语莲。之所以把这两首歌曲&#xff0c;以及这两位音乐人&#xff0c;放在一起来做对比&#xff0c;是因为这两首歌曲&#xff0c;甚至这两位音乐人&a…

Java中weekOfYear和weekOfWeekBasedYear的区别

这其实是计算一年中的周数&#xff08;某日属于一年中的第几周&#xff09;的两种算法。 简单来说&#xff0c;前者保证了1周不会跨越自然年的边界&#xff1b;后者保证了1周一定有7天&#xff0c;一定从某个DayOfWeek&#xff08;如周一&#xff09;开始&#xff0c;并且1周只…

时序预测 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络时间序列预测 目录时序预测 | MATLAB实现GWO-BiLSTM灰狼算法优化双向长短期记忆神经网络时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现GWO-BiLSTM灰狼算法优化双向长短…

每天花2小时恶补腾讯T8纯手打688页SSM框架和Redis,成功上岸美团

前言 我相信大家也都跟我一样&#xff0c;每天不是在加班就是在加班的路上&#xff0c;辛辛苦苦付出&#xff0c;可是得到的却不是很多。 这可能是大部分程序员的现状吧&#xff01;&#xff01; 最关键的是&#xff0c;整天都在CRUD、实现需求&#xff0c;真的想跟产品经理…

广发证券基于分布式架构的新一代估值系统实践

文 / 广发证券信息技术部 来源 / 金融电子化 随着信息技术应用创新试点范围不断扩大&#xff0c;能否胜任更多业务场景&#xff0c;是各行各业当前阶段选型数据库的关键。早在 2019 年&#xff0c;广发证券即开启对分布式架构的数据库产品进行调研&#xff0c;并经历了单主从读…

Git 提交模式

Git 对我们 Devs 的使用是必不可少的&#xff0c;无论是在个人项目中&#xff0c;还是在多人开源项目或整个社区中。鉴于此&#xff0c;正确使用 git commit很重要。拥有连贯和标准化的语言有助于参与项目的每个人理解已经发生的变化。在上图中&#xff0c;我们看到了评论不当的…

Camtasia2023简体中文版支持4K超清屏幕录屏

Camtasia 2023是TechSmith出品的一款屏幕录像和编辑的软件&#xff0c;可轻松录制和分享高质量的截屏视频&#xff0c;提供所需的工具和功能。功能强大的视频编辑器&#xff0c;通过记录您的屏幕活动和网络摄像头流&#xff0c;帮助您创建具有专业外观的截屏视频。软件提供了强…

“华为杯”研究生数学建模竞赛2005年-【华为杯】B题:空中加油问题的递推模型与调度策略

赛题描述 对飞行中的飞机进行空中加油,可以大大提高飞机的直航能力。为了简化问题,便于讨论,我们作如下假设。 少辅机架数两种情况给出你的作战方案。 论文 摘要 : 本文首先对空中加油问题进行了分析,提取了相关性质,在此基础上 建立了问题的递推模型。根据该模型,文…

使用DcokerCompose部署微服务项目详解

一、项目结构 我这里准备了一个微服务项目。 里面包含三个服务模块&#xff1a; card-service、use-rservice以及网关gateway。 一个公共模块&#xff1a; fegin-api。 其中fegin-api被card-service和user-service引用。 二、基于项目构建部署目录结构 这个springcloud-a…

电子档案备份相对于数据备份的特别之处

最近在和档案馆信息部门领导交流过程中&#xff0c;他们提出了一个很实际的困惑&#xff1a;《“十四五”全国档案事业发展规划》&#xff08;以下简称&#xff1a;《十四五规划》&#xff09;中明确提出了“电子档案备份中心”建设&#xff0c;并且要求“扎实做好档案数字资源…

检索业务:排序和价格区间及库存

排序 点击某个排序时首先按升序显示&#xff0c;再次点击再变为降序&#xff0c;并且还会显示上升或下降箭头 页面排序跳转的思路是通过点击某个按钮时会向其class属性添加/去除desc&#xff0c;并根据属性值进行url拼接 点击排序时 $(".sort_a").click(function () …

如何恢复回收站被清空的文件?你必须要知道的4种方法

电脑回收站里面有很多我们之前删除的文件&#xff0c;如果想要恢复这些文件&#xff0c;直接还原就可以了。但是回收站里面的数据被清空了怎么办&#xff1f;如何恢复回收站被清空的文件&#xff1f;接下来就给大家分享一些恢复回收站文件被删除的方法&#xff01; 方法一、使用…

AcWing 1068. 环形石子合并(环形区间DP)

AcWing 1068. 环形石子合并&#xff08;环形区间DP&#xff09;一、问题二、思路三、代码一、问题 二、思路 在讲解这道题之前&#xff0c;我们需要先掌握线性的区间DP问题&#xff0c;如果对于线性区间DP的解决方式还不了解的话&#xff0c;建议先看一下这篇文章&#xff1a;…

概论第7章_参数估计_点估计的评价标准_相合性_无偏性_有效性

点估计的评价标准包括&#xff1a; 相合性&#xff0c; 无偏性&#xff0c; 有效性。 一. 相合性 衡量一个估计是否可行的必要条件&#xff0c; 就是估计的相合性。 本文不提其定义了。直接给出一些结论。 结论 设有正态总体N(μ,σ2\mu, \sigma^2μ,σ2) 的样本&#xff0c;…

数据结构资料汇编:栈

数据结构资料汇编&#xff1a;栈 定义 栈&#xff08;stack&#xff09;是限定仅在表尾进行插入或删除操作的线性表。 表尾称为栈顶&#xff08;top&#xff09;&#xff0c;可以进行插入或删除操作 栈的插入操作称为进栈或入栈&#xff08;push&#xff09;栈的删除操作成为出…

Google Play 上的 Shady Reward 应用累积了 2000 万次下载

一种新的活动跟踪应用程序类别最近在 Android 的官方应用程序商店 Google Play 上取得了巨大成功&#xff0c;已在超过 2000 万台设备上下载。 这些应用程序将自己宣传为健康、计步器和养成良好习惯的应用程序&#xff0c;承诺为用户在日常生活中保持活跃、达到距离目标等提供…

字节青训前端笔记 | 跨端技术概述

本节课程内容会分为以下几个方面&#xff1a; 跨端是什么&#xff0c;给大家介绍跨端产生的背景及解决的问题跨端技术方案介绍&#xff0c;给大家介绍目前主流的跨端技术方案&#xff08;hybrid 方案/原生渲染方案/自渲染方案/小程序方案&#xff09;以及对比基于小程序跨端实…

苹果或将打造 “空气键盘”

苹果MR头显玩法大揭秘前言苹果MR头显要来了打造 “空气键盘”眼动追踪与手部追踪一键切换VR/AR模式前言 随着2021年10月FaceBook正式改名Meta后&#xff0c;标志着元宇宙元年的正式到来&#xff0c;元宇宙行业开始出现井喷式的爆发。再到2022年10月&#xff0c;“飞天云动”在…

欧科云链链上卫士:2023年1月安全事件盘点

一、基本信息 2023年1月安全事件共造成约1438万美元损失&#xff0c;相比上个月的安全事件损失金额大幅度下降。其中多链项目LendHub 被攻击&#xff0c;损失高达600万美元&#xff0c;为本月资金损失最大的安全事件。本月RugPull数量基本与上月持平。社媒诈骗等事件依然频发&a…