考虑分配与合并,用GO实现GCMarkSweep
完整源码 ≧ω≦
希望各位爸爸们,给我点赞吧
kokool/GCByGo: 《垃圾回收的算法与实现》有感而发 (github.com)
书接上文
我们之前不考虑分配与合并情况下,用GO实现GCMarkSweep(标记清除算法),而这次我们继续回顾书本内容的伪代码,修改之前写的内容。
伪代码
首先根据书本的描述,我们可以编写如下的大体的框架
合并阶段
可以看到新增的合并伪代码中,中村成洋作者认为我们的堆sweeping
是无所不能的结构体,它还带有存放一个个地址位置的属性
而这属性还恰好能够匹配上free_list
,相应的也影响了free_list
中表示存放的空闲size
的属性
方便了装8,但是费脑子。
之前的做法中设置数据结构的操作如下:
- 结构体数组
[HEAP_SIZE]Object
实现了堆存放所有对象 - 变量地址形象地表示成
free_list
链表
妥妥地在暗示我去修改它们成结构体,可是太复杂了主要是不会改 ,所以我最终选择把free_list
改成数组,里面的元素表示为存放的chunk
的index
var free_list []int
而因为堆涉及到删除和增加对象元素,再使用结构体数组并不方便,因此要变成结构体切片
//go的语法糖写法
var heap []Object
觉得能搞到这么多就行了,接着分配阶段。
sweep_phase(){sweeping = $heap_startwhile (sweeping < $heap_end)if (sweeping.mark == TRUE)sweeping.mark = FALSEelse//合并if (sweeping == $free_list + $free_list.size)$free_list.size += sweeping.sizeelsesweeping.next = $free_list$free_list = sweepingsweeping += sweeping.size
}
分配阶段
完全照着书本来,发现也没什么特别,就是不明白分配应该在哪里启动?pickup_chunk
函数具体怎么操作的?
new_obj(size){chunk = pickup_chunk(size, $free_list)if (chunk != NULL)return chunkelseallocation_fail()
}pickup_chunk(size, $free_list){
//First - fit策略:最初发现大于等于size 的分块时就会立即返回该分块。for i in range($free_list) if $free_list[i].size == size return heap[i] else if $free_list[i].size > size//屏蔽具体的如何划分操作allocate()
}allocation_fail(){return "抛出错误,没有找到合适的分块"
}
结果全篇下来就会实现allocation_fail()
函数
GCMarkSweep\Considered\PKG\allocate.go
func allocation_fail() {//暂时不知道怎么搞,先returnpanic("all not-active object have not enough size")
}
具体实现
看到它这么喜欢加属性,而且也不清楚分配操作到底要干嘛的迷惑操作,正常人的思维都被绕进去了。
但是我哪是什么常人,因为人类终究是有极限的!!
创建对象
想了想,我干嘛要死磕,于是我选择了跳过,还真不是摆烂 ,后面发现了作者写的一段话。
OK,这句话就明确说明了所谓的分配其实就是创建数据->重写之前的数据初始化操作
相应的初始化数据集进行修改即可。
但是我的朋友,有些内容还是有遗漏,堆的对象数量都是我们一开始人为设置好的,我们应该是要想办法让它们根据堆的size
来自己规划好最终对象的数量,但是又要考虑实现难度,于是就有了如下的小demo。
BASE_SPACE
初步规定好的堆的空间BASE_NUM
初步规定好的对象数量init_space
统计所有初始化后对象的size
GCMarkSweep\Considered\PKG\create_data.go
func Init_data(BASE_NUM, BASE_SPACE int) {for i := 0; i < BASE_NUM; i++ {...}//对象指向的对象(活动对象)//对象指向的对象(非活动对象)//判断堆的初始化的空间够不够?if BASE_SPACE < init_space {panic("堆不够空间分配对象")} else if BASE_SPACE > init_space { //最终剩下的空间用来新增一个对象heap = append(heap, *NewObject("Data", nil, BASE_SPACE-init_space))}//roots指向的对象.
}
尽管还是人为定义了堆的空间,但是加了一定的约束条件,应该较符合作者的期望了,就当它可以吧。
GCMarkSweep\Considered\PKG\allocate.go
func newObject(node_type string, children []int, size int) *Object {if node_type == "Null" {if children == nil {children = []int{-100}}} else {//统计所有初始化用的sizeinit_space += sizeif size <= 0 {panic("分配不正确")}if children == nil {children = []int{11}}}return &Object{node_type: newNodeType(node_type), children: children, size: size, next_freeIndex: -100, marked: false}
}
分配操作
但是,所列娃多卡纳,我们创建的这不是分块chunk
啊!
于是仔细想想,之前不考虑分配与合并操作中的代码,我们是认为那些最终都放在链表free_list
都是chunk
,它们具有的统一特征:
next_freeIndex
表示上一个分块chunk
的index
children
中都为[]int{-1}
表示被消除所以无关链表的指针和数据
而作者这里写的chunk
跟我们上面表达的意思根本不一样,它的意思是在有空闲size
的情况下,把上面的chunk
变成object
。这就是所谓的分配。
那么实现道路其实很清晰了,具体的修改如下
GCMarkSweep\Considered\PKG\allocate.go
func NewObject(node_type string, children []int, size int) *Object {//free_list不为空时就表示已经初始化了数据,空间分配就那么多,就只能用这些还可以用的空间if free_list != nil {chunk := pickup_chunk(node_type, children, size, free_list)if chunk != nil {return chunk} else {allocation_fail()}} else {object := newObject(node_type, children, size)return object}return nil
}
好奇的你可能会疑问了,为什么讲了那么多,pickup_chunk
函数还没有实现呢?
我知道你很急,但是你先别急
分配的前提是要有空闲分块
->free_list
不为空->free_list
要经过sweep阶段才能诞生->sweep()
新增了合并操作。
而pickup_chunk
函数离不开free_list
作为形参输入,所以我们接下来应该要实现合并操作才对。
合并操作
书本的伪代码认为heap
应该是每次循环得到一个连续空闲的chunk
时,我们都应该进行一次合并,把它们的size
加在一起,把两个对象合二为一。
说白了就是删除object
,但是已经选择结构体切片作为heap
的数据结构,任何一次元素的修改和移动都会影响结果,所以我们应该要在生成完所有的chunk
和装填chunk
的free_list
之后进行操作。
GCMarkSweep\Considered\PKG\mark_sweep.go
而合并分三步走
GCMarkSweep\Considered\PKG\coalese.go
func coalescing() {//第一步:修改各对象的size,获取每次修改涉及到的两个对象的index,将它们变成区间intervals := changeSize()//第二步:合并区间操作m := merge(intervals)//第三步:利用切片和循环来合并成新的堆,更新成合并的新对象与free_list的next_freeIndexnewHeap(m)
}
第一步
利用计数器区分我们要保留的对象和最终要被删除的对象
func changeSize() [][]int {var count int = 0for index := range free_list {if index != 0 {i := free_list[index]if free_list[index-1] == i-1 {count++heap[i-count].size += heap[i].sizeintervals = append(intervals, []int{i - count, i})} else {count = 0}}}return intervals
}
第二步
复制黏贴直接拿别人的代码
//合并区间操作
//https://leetcode.cn/problems/merge-intervals/solution/shou-hua-tu-jie-56he-bing-qu-jian-by-xiao_ben_zhu/
func merge(intervals [][]int) [][]int {sort.Slice(intervals, func(i, j int) bool {return intervals[i][0] < intervals[j][0]})res := [][]int{}prev := intervals[0]for i := 1; i < len(intervals); i++ {cur := intervals[i]if prev[1] < cur[0] { // 没有一点重合res = append(res, prev)prev = cur} else { // 有重合prev[1] = max(prev[1], cur[1])}}res = append(res, prev)return res
}func max(a, b int) int {if a > b {return a}return b
}
第三步
利用切片删除对象的操作
func newHeap(m [][]int) {//合并var heap_copy []Objectfor i := 0; i < len(m)-1; i++ {hj := heap[:m[i][0]+1]hk := heap[m[i][1]+1 : m[i+1][0]+1]for j := range hj {heap_copy = append(heap_copy, hj[j])}for k := range hk {heap_copy = append(heap_copy, hk[k])}}change_Index_list(heap_copy)
}//更改next_freeIndex和更改free_list
func change_Index_list(heap_copy []Object) {var list []intvar next_index int = -1for i := range heap_copy {if heap_copy[i].children[0] == -1 {list = append(list, i)heap_copy[i].next_freeIndex = next_indexnext_index = i}}heap = heap_copyfmt.Println(heap)free_list = listfmt.Println(free_list)
}
你学废了嘛?
pickup_chunk函数
那么在清楚了free_list
怎么来之后,就可以根据伪代码实现了
GCMarkSweep\Considered\PKG\allocate.go
func pickup_chunk(node_type string, children []int, needSize int, freeList []int) *Object {// fmt.Println(needSize, freeList)var heap_copy []Objectfor i := range freeList {index := freeList[i]//刚刚好就直接返回这个对象if heap[index].size == needSize {return &heap[index]//如果空间较大就要分块} else if heap[index].size > needSize {heap[index].size -= needSizeobj := *newObject(node_type, children, needSize)//注意:不能利用切片append插入,否则引用传递,我指的是append(heap_copy,heap[:index],obj)这样的写法for j := 0; j < index; j++ {heap_copy = append(heap_copy, heap[j])}heap_copy = append(heap_copy, obj)for k := index; k < len(heap); k++ {heap_copy = append(heap_copy, heap[k])}change_Index_list(heap_copy)return &heap[index]}}return nil
}
图解演示
初始化
恭喜你读完了以上的废话,让我们到了最激动人心的演示过程,先创建数据集
func Init_data(BASE_NUM, BASE_SPACE int) {for i := 0; i < BASE_NUM; i++ {no := NewObject("Null", nil, 0)heap = append(heap, *no)}//对象指向的对象(活动对象)heap[0] = *NewObject("Data", []int{11}, 2)// heap[3] = *newObject("Key", []int{5, 4}, 2)heap[3] = *NewObject("Key", []int{4}, 2)heap[4] = *NewObject("Data", []int{11}, 2)//对象指向的对象(非活动对象)heap[5] = *NewObject("Data", []int{11}, 2)heap[1] = *NewObject("Data", []int{11}, 2)heap[2] = *NewObject("Data", []int{11}, 2)heap[6] = *NewObject("Data", []int{11}, 2)//判断堆的初始化的空间够不够?if BASE_SPACE < init_space {panic("堆不够空间分配对象")} else if BASE_SPACE > init_space { //最终剩下的空间用来新增一个对象heap = append(heap, *NewObject("Data", nil, BASE_SPACE-init_space))}//roots指向的对象.// roots = []int{1, 3}roots = append(roots, 0)roots = append(roots, 3)// fmt.Println(heap)// fmt.Printf("类型是%T\n", heap)}
启动
func main() {MSC.Init_data(7, 20)fmt.Println("### init ###")MSC.Print_data()MSC.Mark_phase()fmt.Println("### mark phase ###")MSC.Print_data()MSC.Sweep_phase()fmt.Println("### sweep phase ###")MSC.Print_data()//测试分配_ = MSC.NewObject("Data", []int{22}, 6)
}
约束
设定初始对象数量为7
个,空间size
有20
大小
图像表示如下
可以看到了object7
是新生成的,它的size
大小为6
,即BASE_SPACE-init_size
,具体数值是20-14=6
mark
该打勾的地方都打勾了
sweep
可以看到我们的对象最终减少到它应有的最少数量,size
的大小也是相邻合并,而且next_freeIndex
并没有出错,这也同时体现在存放chunk_index
的free_list
上
allocate
策略是:
heap
的size
刚刚好等于自己新增的对象就直接返回- 大于就新增一个分块
白框的对象就表示成chunk
变成了active object
了
致谢
感谢您的耐心观看,如果有什么不懂可以在评论区评论(当然我不一定会回答,纯粹就是因为我菜而已)
考虑
没测试过大量的数据集,所以有可能会存在问题,希望细心的读者可以帮忙发现吧。
参考
[1] 《垃圾回收的算法与实现》
[2] https://leetcode.cn/problems/merge-intervals/solution/shou-hua-tu-jie-56he-bing-qu-jian-by-xiao_ben_zhu/
[3] https://www.perfcode.com/p/golang-struct-slice-pointer.html
相关文章:

考虑分配与合并,用GO实现GCMarkSweep
完整源码 ≧ω≦ 希望各位爸爸们,给我点赞吧 kokool/GCByGo: 《垃圾回收的算法与实现》有感而发 (github.com) 书接上文 我们之前不考虑分配与合并情况下,用GO实现GCMarkSweep(标记清除算法),而这次我们继续回顾书本…...

浙江大学海宁IMBA提面经验分享
先来介绍一下我的个人情况:本人毕业于浙江一所普通的本科院校,毕业已经6年了,在一家互联网公司担任市场部经理。其实在参加浙大IMBA项目提面之前,我也参加了浙大MBA项目的提面,可惜只拿到了良好的结果,所以…...

Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
一: 前言 我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。 1:背景 我们基于Mybatis作为后台Orm框架进行编码的时候,有两种方式。 //编码方式1 UserDao userDao sqlSession.getMapper(UserDao.class); userDao.quer…...

JDBC的API详解
🍎道阻且长,行则将至。🍓 目录 一、DriverManager 驱动管理类 1.注册驱动 2.获取数据库连接 二、Connection 数据库连接对象 1.获取执行对象 2.事务管理 三、Statement 1.执行DDL、DML语句 2.执行DQL语句 四、ResultSet 以JDBC快速…...

【深度强化学习】(4) Actor-Critic 模型解析,附Pytorch完整代码
大家好,今天和各位分享一下深度强化学习中的 Actor-Critic 演员评论家算法,Actor-Critic 算法是一种综合了策略迭代和价值迭代的集成算法。我将使用该模型结合 OpenAI 中的 Gym 环境完成一个小游戏,完整代码可以从我的 GitHub 中获得…...

SQL注入——文件上传
目录 一,mysql文件上传要点 二,文件上传指令 一句话木马 三,实例 1,判断注入方式 2,测试目标网站的闭合方式: 3,写入一句话木马 4,拿到控制权 一,mysql文件上传…...

【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(新的开篇)”】
目录【ESP32freeRTOS学习笔记】系列新的开篇ESP-IDF对FreeRTOS的适配ESP-IDF环境中使用FreeRTOS的差异性简介关于FreeRTOS的配置关于ESP-IDF FreeRTOS Applications结语【ESP32freeRTOS学习笔记】系列新的开篇 ESP-IDF对FreeRTOS的适配 FreeRTOS是一个可以适用于多个不同MCU开…...

Uipath Excel 自动化系列18-RefreshPivotTable(刷新透视表)
活动描述 RefreshPivotTable(刷新透视表):如果透视表的数据源发生变化,需使用刷新透视表活动,该活动需与Use Excel File 活动选择的 Excel 文件一起使用。 使用如下图: RefreshPivotTable(刷新透视表)属性 属性 作用 Display…...

设计模式之不变模式
在并行软件开发过程中,同步操作是必不可少的。当多线程对同一个对象进行读写操作时,为了保证对象数据的一致性和正确性,有必要对对象进行同步操作,但同步操作对系统性能有损耗。不变模式可以去除这些同步操作,提高并行…...

C++11 map
C11中Map的使用Map是c的一个标准容器,她提供了很好一对一的关系,在一些程序中建立一个map可以起到事半功倍的效果,总结了一些map基本简单实用的操作!1. map最基本的构造函数;map<string , int >mapstring; map&l…...

docker基本命令 - 数据卷
作用 ● 做数据持久化。防止容器一旦停止运行,该容器中运行产生的数据就没了 ● 不同容器之间的数据共享(大鲸鱼背上各个小集装箱之间可以共享数据) 交互式命令使用 docker run -it -v / 宿主机的绝对路径目录:/容器内绝对路径目录 镜像名 docker run -it -v / 宿…...

SQL查漏补缺
有这么一道题,先看题目,表的内容如下 显示GDP比非洲任何国家都要高的国家名称(一些国家的GDP值可能为NULL)。 错误的查询: SELECT name FROM bbcWHERE gdp > ALL (SELECT gdp FROM bbc WHERE region Africa)正确的查询: SE…...

偏向锁撤销
偏向状态 一个对象创建时: 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0。偏向锁是默认是延迟的,不会在程序启动时立…...

Qt版海康MV多相机的采集显示程序
创建对话框工程MultiCamera工程文件MultiCamera.pro#------------------------------------------------- # # Project created by QtCreator 2023-03-11T16:52:53 # #-------------------------------------------------QT core guigreaterThan(QT_MAJOR_VERSION, 4): …...

2023年江苏省职业院校技能大赛中职网络安全赛项试卷-教师组任务书
2023年江苏省职业院校技能大赛中职网络安全赛项试卷-教师组任务书 一、竞赛时间 9:00-12:00,12:00-15:00,15:00-17:00共计8小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 基础设施设置与安全加固、网络安全事件响应、数…...

零基础小白如何自学网络安全成为顶尖黑客?
在成为黑客之前,你需要做两点准备: 1、学一门编程语言。学哪一门不重要,但你要参考一下下面的条例: C语言是Unix系统的基础。它(连同汇编语言)能让你学习对黑客非常重要的知识:内存的工作原理…...

外贸建站如何提高搜索引擎排名,吸引更多潜在客户?
在如今全球贸易日益繁荣的背景下,越来越多的企业开始重视外贸建站,并寻求提高搜索引擎排名以吸引更多潜在客户。 那么,如何才能有效地提高外贸网站的搜索引擎排名呢?本文将为您详细介绍几个有效的方法。 一、关键词优化 关键词…...

计算机网络考研-第一章学
计算机网学习总结第一章计算机系统概述1.1.1 导学1.1.2 操作系统的特征1.2 操作系统的发展与分类1.3 操作系统的运行环境1.3.1 操作系统的运行机制1.3.2 中断和异常1.3.3系统调用:1.3.4 操作系统的体系结构第一章总结第一章计算机系统概述 1.1.1 导学 1.1.2 操作系…...

【分布式版本控制系统Git】| Git概述、Git安装、Git常用命令
目录 一:概述 1.1. 何为版本控制 1.2. 为什么需要版本控制 1.3. 版本控制工具 1.4. Git 简史 1.5. Git 工作机制 1.6. Git和代码托管中心 二:安装 2.1. Git安装 三:常用命令 3.1 设置用户签名 3.2 初始化本地库 3.3 查看本地库…...

【人脸识别】ssd + opencv Eigenfaces 和 LBPH算法进行人脸监测和识别
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言ssd opencv Eigenfaces 和 LBPH算法进行人脸监测和识别1. ssd 目标监测2.opencv的三种人脸识别方法2.1 Eigenfaces2.2 LBPH前言 ssd opencv Eigenfaces 和 LB…...

PMP项目管理项目成本管理
目录1 项目成本管理概述2 规划成本管理3 估算成本4 制定预算5 控制成本1 项目成本管理概述 项目成本管理包括为使项目在批准的预算内完成而对成本进行规划、估算、预测、融资、筹资、管理和控制的各个过程,从而确保项目在批准的预算内完工。核心概念 项目成本管理旨…...

Vue3视频播放器组件Vue3-video-play入门教程
Vue3-video-play适用于 Vue3 的 hls.js 播放器组件 | 并且支持MP4/WebM/Ogg格式。 1、支持快捷键操作 2、支持倍速播放设置 3、支持镜像画面设置 4、支持关灯模式设置 5、支持画中画模式播放 6、支持全屏/网页全屏播放 7、支持从固定时间开始播放 8、支持移动端,移动…...

操作系统经典问题——消费者生产者问题
今日在学习操作系统的过程中遇到了这个问题,实在是很苦恼一时间对于这种问题以及老师上课根据这个问题衍生的问题实在是一头雾水。在网络上寻找了一些大佬的讲解之后算是暂时有了点茅塞顿开的感觉。 首先第一点什么是生产者——消费者问题: 系统中有一…...

网络安全工程师在面试安全岗位时,哪些内容是加分项?
金三银四已经来了,很多小伙伴都在困惑,面试网络安全工程师的时候,有哪些技能是加分项呢?接下来,我简单说说! 去年我在微博上贴了一些在面试安全工作时会加分的内容,如下: 1. wooyu…...

前端整理 —— vue
1. vue的生命周期 经典爱问,感觉内容挺多的 介绍一下有哪几个 vue2中的生命周期有11个,分别为beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy&…...

HTML快速入门
目录HTML概念HTML基本格式基本语法常用标签1.文件标签:构成html最基本的标签2.文本标签:和文本有关的标签3.列表标签4.图片标签5.超链接标签6.表格标签7.表单标签HTML概念 HTML是最基础的网页开发语言,Hyper Text Markup Language࿰…...

哈希冲突
为什么会有哈希冲突?哈希表通过哈希函数来计算存放数据,在curd数据时不用多次比较,时间复杂度O(1)。但是凡事都有利弊,不同关键字通过相同哈希函数可能计算出来相同的存放地址,这种现象被称为哈…...

git添加子模块(submodule)
git添加子模块(submodule) 背景 有时候自己的项目需要用到别人的开源代码,例如 freertos 和 tinyusb 这个时候有两种选择 将开源的代码下载下来放到自己的 git 中管理 缺点:如果远端仓库更新,自己仓库的代码不会更新 将开源代码通过子模块…...

C++ 11 pair
class pair 可将两个 value视为一个单元。C标准库内多处用到了这个 class 。尤其是容器 map、multimap、unordered_map和 unordered_multimap就是使用 pair 来管理其以 key/value pair形式存在的元素。任何函数如果需要返回两个 value,也需要用到 pair,例…...

反向传播与随机梯度下降
反向传播实际上就是在算各个阶段梯度,每层的传入实际是之前各层根据链式法则梯度相乘的结果。反向传播最初传入的Δout是1,Δ通常表示很少量的意思,Δout1的时候这样在反向传播的时候算出来的dw和dx刚好就是当前梯度。深度神经网络中每层都会…...