考虑分配与合并,用GO实现GCMarkSweep

news2024/12/30 1:36:18

完整源码 ≧ω≦

希望各位爸爸们,给我点赞吧
kokool/GCByGo: 《垃圾回收的算法与实现》有感而发 (github.com)

书接上文

我们之前不考虑分配与合并情况下,用GO实现GCMarkSweep(标记清除算法),而这次我们继续回顾书本内容的伪代码,修改之前写的内容。

伪代码

首先根据书本的描述,我们可以编写如下的大体的框架

合并阶段

可以看到新增的合并伪代码中,中村成洋作者认为我们的堆sweeping无所不能的结构体,它还带有存放一个个地址位置的属性
请添加图片描述

而这属性还恰好能够匹配上free_list,相应的也影响了free_list中表示存放的空闲size的属性

方便了装8,但是费脑子。

之前的做法中设置数据结构的操作如下:

  • 结构体数组[HEAP_SIZE]Object实现了堆存放所有对象
  • 变量地址形象地表示成free_list链表

妥妥地在暗示我去修改它们成结构体,可是太复杂了主要是不会改 ,所以我最终选择把free_list改成数组,里面的元素表示为存放的chunkindex

var free_list []int

而因为堆涉及到删除和增加对象元素,再使用结构体数组并不方便,因此要变成结构体切片

//go的语法糖写法
var heap []Object

觉得能搞到这么多就行了,接着分配阶段。

sweep_phase(){
    sweeping = $heap_start
    while (sweeping < $heap_end)
        if (sweeping.mark == TRUE)
            sweeping.mark = FALSE
        else
        //合并
            if (sweeping == $free_list + $free_list.size)
                $free_list.size += sweeping.size
        else
            sweeping.next = $free_list
            $free_list = sweeping
    sweeping += sweeping.size
}

分配阶段

完全照着书本来,发现也没什么特别,就是不明白分配应该在哪里启动?pickup_chunk函数具体怎么操作的?

new_obj(size){
    chunk = pickup_chunk(size, $free_list)
    if (chunk != NULL)
        return chunk
    else
        allocation_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() {
	//暂时不知道怎么搞,先return
	panic("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 {
		//统计所有初始化用的size
		init_space += size
		if 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表示上一个分块chunkindex
  • 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和装填chunkfree_list之后进行操作。

GCMarkSweep\Considered\PKG\mark_sweep.go
在这里插入图片描述

而合并分三步走
GCMarkSweep\Considered\PKG\coalese.go

func coalescing() {
	//第一步:修改各对象的size,获取每次修改涉及到的两个对象的index,将它们变成区间
	intervals := changeSize()
	//第二步:合并区间操作
	m := merge(intervals)
	//第三步:利用切片和循环来合并成新的堆,更新成合并的新对象与free_list的next_freeIndex
	newHeap(m)
}

第一步

利用计数器区分我们要保留的对象和最终要被删除的对象

func changeSize() [][]int {
	var count int = 0
	for 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].size
				intervals = 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 []Object
	for 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 []int
	var next_index int = -1
	for i := range heap_copy {
		if heap_copy[i].children[0] == -1 {
			list = append(list, i)
			heap_copy[i].next_freeIndex = next_index
			next_index = i
		}
	}
	heap = heap_copy
	fmt.Println(heap)
	free_list = list
	fmt.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 []Object
	for i := range freeList {
		index := freeList[i]
		//刚刚好就直接返回这个对象
		if heap[index].size == needSize {
			return &heap[index]
			//如果空间较大就要分块
		} else if heap[index].size > needSize {
			heap[index].size -= needSize
			obj := *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个,空间size20大小

图像表示如下
请添加图片描述
可以看到了object7是新生成的,它的size大小为6,即BASE_SPACE-init_size,具体数值是20-14=6
在这里插入图片描述

mark

请添加图片描述
该打勾的地方都打勾了
在这里插入图片描述

sweep

可以看到我们的对象最终减少到它应有的最少数量,size的大小也是相邻合并,而且next_freeIndex并没有出错,这也同时体现在存放chunk_indexfree_list
在这里插入图片描述

allocate

策略是:

  • heapsize刚刚好等于自己新增的对象就直接返回
  • 大于就新增一个分块

白框的对象就表示成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

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

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

相关文章

SPI 接口OLED 模块 - 兼容5V 和3.3V 电平

PCB 布局参考了老王0.8元128x32OLED显示屏转接板&#xff0c;开源项目地址&#xff1a;老王0.8元128x32OLED 模块-部分优化。 老王家买的屏幕放了快一年了&#xff0c;终于还是决定整个单独的模块&#xff0c;之前一直打算集成到开发板上的&#xff0c;不太灵活。相比那个转接板…

jenkins扩展你的流水线

文章目录一、概述二、可信库和不可信库可信库不可信库三、内部库与外部库内部库SSH访问HTTP 访问外部库配置一个外部库四、在流水线脚本中使用库从源码版本控制中自动下载库加载库到脚本中Library 注解库步骤库指令五、Jenkins 项目中的库范围六、共享库代码的结构src示例一&am…

Java启蒙之语言基础

目录 一.Java标识符和关键字 1.1Java标识符 1.2Java关键字 二.数据类型和变量的概述和关系 2.1Java变量 2.2Java的数据类型 2.2.1数据类型的分类的概述 2.2.2数据类型的转换 3.Java运算符 总结 &#x1f63d;个人主页&#xff1a;tq02的博客_CSDN博客-领域博主 &#…

帧中继多点子接口配置

帧中继多点子接口配置 拓扑图&#xff1a; 设备参数&#xff1a; 设备 接口 DLCI 设备 接口 DLCI R1 S0/0/0 102 R2 S0/0/0 201 R1 S0/0/0 103 R3 S0/0/0 301 IP参数&#xff1a; 设备 接口 IP地址 子网掩码 默认网关 R1 S0/0/0.1 192.168.123.1 25…

荧光成像技术原理ICG-PEG-N3/COOH/NH2/Alkyne吲哚菁绿-聚乙二醇-叠氮

品牌&#xff1a;为华生物产地&#xff1a;广州产品名称&#xff1a;吲哚菁绿-聚乙二醇-叠氮英文名称&#xff1a;ICG-PEG-N3PEG分子量&#xff1a;600、800、1000、2000、3400、5000、10000质量&#xff1a;95%激发波长&#xff1a;785nm发射波长&#xff1a;821nm外观&#x…

【算法基础】最短路算法(朴素Dijkstra + 堆优化Dijkstra + Bellman-Ford +SPFA + Floyd)

一、最短路算法 1. 朴素Dijkstra算法 Dijkstra算法 用来求 所有边权都是正数 的 单源最短路。边权 即两个点之间的距离;单源, 即只求从源点(起点,终点也称为汇点)到其他点的最短距离; 朴素Dijkstra 算法适用于求 稠密图 的最短距离问题。稠密图是指边数有很多的图,假设…

Flask-mock接口数据流程

背景&#xff1a;由于在开发过程中&#xff0c;会遇到以下的痛点 1.服务端接口提测延期&#xff0c;具体接口逻辑未完成实现&#xff0c;接口未能正常调通&#xff0c;导致客户端提测停滞&#xff1b; 2.因为前期已在技术评审上已与客户端开发定好接口字段&#xff0c;客户端比…

【蓝桥杯-筑基篇】基础数学思维与技巧(2)

&#x1f353;系列专栏:蓝桥杯 &#x1f349;个人主页:个人主页 目录 &#x1f36a;1.判断素数&#x1f36a; &#x1f966;2.大整数&#x1f966; &#x1f34b;3.求n的约数个数&#x1f34b; &#x1f349;4.数学归纳法&#x1f349; &#x1f353;5.阶乘之和&#x1f3…

使用 typora软件 编写 markdown 写作的技巧

typora 编写 markdown 写作的技巧 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 使用typora 编写 markdown 写作的时间久了后&#xff0c;会发现一些技巧 &#xff0c;分享给大家 …

LeetCode206_206. 反转链表

LeetCode206_206. 反转链表 一、描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&…

结构体内存大小

000、前言 要想计算结构体内存大小&#xff0c;就会涉及到一个结构体内存对齐的问题&#xff0c;而不是对其成员进行简单的加运算 &#xff08;1&#xff09;在写本博客之前 有位同学和我讨论了一个学校的题目&#xff0c;题目如下&#xff1a; 我借这道题目问了另外一位同…

JS学习笔记day05(完结)!

今日内容 零、 复习昨日 一、作业 二、BOM 三、定时器 四、正则表达式 零、 复习昨日 事件 事件绑定方式鼠标事件 onmouseoveronmouseoutonmousemove 键盘事件 onkeydownonkeyuponkeypress 表单事件 onfocusonbluronchangeonsubmit 页面加载事件 onload dom dom树查找dom do…

what data contract

URLS 笔记内容主要来自 https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/cloud-scale-analytics/architectures/data-contracts Data contracts Data contracts are similar to service contracts or data delivery contracts.A contract also…

One-YOLOv5 v1.2.0发布:支持分类、检测、实例分割

One-YOLOv5 v1.2.0正式发布。完整更新列表请查看链接&#xff1a;https://github.com/Oneflow-Inc/one-yolov5/releases/tag/v1.2.0&#xff0c;欢迎体验新版本&#xff0c;期待你的反馈。 1 新版本特性 1. 同步了Ultralytics YOLOv5的上游分支v7.0&#xff0c;同时支持分类、目…

多校园SaaS运营智慧校园云平台源码 智慧校园移动小程序源码

智慧校园管理平台源码 智慧校园云平台源码 智慧校园全套源码包含&#xff1a;电子班牌管理系统、成绩管理系统、考勤人脸刷卡管理系统、综合素养评价系统、请假管理系统、电子班牌发布系统、校务管理系统、小程序移动端、教师后台管理系统、SaaS运营云平台&#xff08;支持多学…

每天学一点之Stream流相关操作

StreamAPI 一、Stream特点 Stream是数据渠道&#xff0c;用于操作数据源&#xff08;集合、数组等&#xff09;所生成的元素序列。“集合讲的是数据&#xff0c;负责存储数据&#xff0c;Stream流讲的是计算&#xff0c;负责处理数据&#xff01;” 注意&#xff1a; ①Str…

Java面试总结(六)

进程和线程的区别 根本区别&#xff1a; 进程时操作系统资源分配的基本单位&#xff0c;而线程是处理器任务调度和执行的基本单位。 资源开销&#xff1a; 每个进程都有自己独立的代码和数据空间&#xff08;程序上下文&#xff09;&#xff0c;进程之间的切换开销比较大&…

狂扫近300万读者,蟒蛇书升级版即将出版,招募审读人

狂扫全世界近 300 万爱好者成为编程领域的现象级爆品豆瓣累计超过 5000 人评价第2版中文版获得了 9.3 分的好评Amazon 近 10000 人评价第2版原版获得了 4.7 星好评毫不夸张&#xff0c;它是全世界读者心中的 Python 入门圣经因为封面上是一只蠢萌的蟒蛇这本书又被读者亲切地称为…

SQL注入——文件上传

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

【面试系列】volatile的底层原理

并发编程的三大特性 原子性可见性原子性 JAVA内存模型 Java内存模型&#xff08;Java Memory Model&#xff09;主要分为主内存和线程工作内存。 主内存&#xff1a;方法区和堆空间 线程工作内存&#xff1a;虚拟机栈&#xff0c;本地方法栈&#xff0c;程序计数器。 所有…