[Garbage collector] Implementation of reference counting method (ReferenceCount) based on Go

If you don't want to spread the fire, you can click the link below!

github:GCByGO

Give me a thumbs up, ball ball!
Please add a picture description

What This?

Phenomenon

Reference counting is a garbage collection algorithm that tracks how many times an object is referenced. In this algorithm, each object maintains a counter that records how many pointers point to it.

When a new pointer refers to an object, the object's counter is incremented by 1, and when the pointer is freed, the object's counter is decremented by 1.

When the object's counter becomes 0, it means that the object does not have any pointers to it, which means that the object can be recycled.

Reference counting regularly scans objects in memory and reclaims objects with a counter of 0.

Advantages and disadvantages

  • Advantages (fast) : The advantage of the reference counting method is that the timing of garbage collection can be very fast, because the counter of the object is updated in real time.
  • Disadvantage (circular reference) : If there is a circular reference, that is, two or more objects refer to each other, but no other object refers to them, then the counters of these objects will never become 0, and they cannot be recycled, which will cause memory leakage.

Therefore, the reference counting method usually needs to be used together with other garbage collection algorithms to solve the problem of circular references.

example

Some people must be impatient to read the text! But who told me to be kind? I don't want to see you in such pain.

insert image description here

The composition of the counter is actually very simple, that is, there are only counters and domains.

  • Domain: specifically stores the reference relationship between its own node and other nodes, that is,自己的指针->其他节点
  • Counter: counts the number of references
    insert image description here

The two pictures below are very vivid.

At the beginning, we created the four nodes ABCD, where C is referenced by A
Please add a picture description
and then the result after modification A->Cis A->Bas follows;
Please add a picture description
have you learned it?

accomplish

pseudocode

update_ptr()Function: used to update the pointer ptr to point to the object obj, and increase or decrease the counter value at the same time.
Notice:

The ptr here has two meanings, which means that the modification as shown in the above figure A->Cbecomes A->Bthe two different pointers involved.

In addition, it is necessary to ensure that it is increased first and then decreased, so as to avoid ptraccidental objdeletion caused by the same object.
insert image description here
inc_ref_cnt() Function: Counter increment operation.
insert image description here
dec_ref_cnt()Function: Counter decrement operation.
insert image description here

GO implementation

Directory structure :

ReferenceCount
	|		|
	|	main.go
	|
	----->PKG
		   |
		   ------>allocate.go
		   ------>createData.go
		   ------>freeLinkedList.go
		   ------>object.go

linked listfreeLinkedList.go

FreeLinkedListA data structure of is defined , which is a data structure based on a linked list, and the nodes in it are defined as Nodestructures. The head node of the linked list is a pointer to the first node.

NodeThe structure contains a dataproperty , which is used to store the data value of the node, and a next property, which points to the next node. String()The method implements the function of formatting and outputting the data value of the node.

type Node struct {
    
    
	data interface{
    
    }
	next *Node
}

type FreeLinkedList struct {
    
    
	head *Node
}

This FreeLinkedListdata structure provides the following methods:

  • insertAtEnd: Insert a new node at the end of the linked list, and the data value of the new node is the incoming parameter.
func (list *FreeLinkedList) insertAtEnd(data interface{
    
    }) {
    
    
	newNode := &Node{
    
    data: data}
	if list.head == nil {
    
    
		list.head = newNode
	} else {
    
    
		current := list.head
		for current.next != nil {
    
    
			current = current.next
		}
		current.next = newNode
	}
}
  • deleteNode: Delete a node in the linked list, the data value of the deleted node is the incoming parameter.
func (list *FreeLinkedList) deleteNode(data interface{
    
    }) {
    
    
	if list.head == nil {
    
    
		return
	}
	if list.head.data == data {
    
    
		list.head = list.head.next
		return
	}
	prev := list.head
	current := list.head.next
	for current != nil {
    
    
		if current.data == data {
    
    
			prev.next = current.next
			return
		}
		prev = current
		current = current.next
	}
}
  • printLinkedList: Print the entire linked list, and output the data value of each node sequentially from the head node.
func (n *Node) String() string {
    
    
	return fmt.Sprintf("%v", n.data)
}

func (list *FreeLinkedList) printLinkedList() {
    
    
	current := list.head
	for current != nil {
    
    
		fmt.Printf("%v->", current.data)
		current = current.next
		if current == nil {
    
    
			fmt.Print("nil\n")
		}
	}
}

core codeobject.go

A Objectstructure , which contains a number no, a reference counter refCnt, a byte array data, and an array of sub-objects children.

type Object struct {
    
    
	no       string
	refCnt   int
	data     []byte
	children []*Object
}

This structure provides the following methods:

  • getInterface: Returns the number and the length of the data byte array. The reason why a getter method is written here is because I defined datathe type of its storage in the linked list before interface.

Because in Gothe language, interface{}it is a unified type that can be used to represent any type of value.
However, since no methods or properties are defined, no properties or methods interface{}can be accessed directly on it .
If you want to interface{}call a property on a variable of a type
, first cast it to the type of the method, then a interface{}variable of the type that implements some struct
that has GetInterfacemethods, and use the predicate cast to get its value. .

func (obj *Object) getInterface() (string, int) {
    
    
	return obj.no, len(obj.data)
}
  • updatePtr: Update a pointer to Object, update the related reference counter at the same time, remove the object pointed to by the original pointer from its sub-object array, and add it to the free list when its reference counter drops to 0.
func (obj *Object) updatePtr(oldptr *Object, ptr *Object, list *FreeLinkedList) {
    
    
	if ptr == nil {
    
    
		return
	}
	ptr.incRefCnt()
	oldptr.decRefCnt(list)
	obj.children = []*Object{
    
    ptr}
}
  • incRefCnt: Increment the reference counter.
func (obj *Object) incRefCnt() {
    
    
	if obj == nil {
    
    
		return
	}
	obj.refCnt++
}
  • decRefCnt: Decrement the reference counter. If the reference counter drops to 0, it recursively calls decRefCntthe method and adds itself to the free list.
func (obj *Object) decRefCnt(list *FreeLinkedList) {
    
    
	if obj == nil {
    
    
		return
	}
	obj.refCnt--
	if obj.refCnt == 0 {
    
    
		for _, child := range obj.children {
    
    
			child.decRefCnt(list)
		}
		obj.reclaim(list)
	}
}
  • AddRef: Add a sub-object pointer and increase its reference counter ( note here, when creating an object, I will not let refCnt become 1 ).
func (obj *Object) AddRef(ptr *Object) {
    
    
	if ptr == nil {
    
    
		return
	}
	obj.children = append(obj.children, ptr)
	ptr.incRefCnt()
}
  • reclaim: Add the object to the free list and print a message.
func (obj *Object) reclaim(list *FreeLinkedList) {
    
    
	obj.children = nil
	fmt.Printf("%s has been reclaimed\n", obj.no)
	//这里就加入空闲链表中
	list.insertAtEnd(obj)
}

distributeallocate.go

Implemented a memory allocation and recovery mechanism. in:

  • newObjectFunction: Receive the number and size of the object, then try to find the available space from the free list to allocate to the object, and return the pointer of the object. If no free space is found, the trigger allocation_failfunction throws panic.
func newObject(no string, size int, list *FreeLinkedList) *Object {
    
    
	obj := pickupChunk(no, size, list)
	if obj == nil {
    
    
		allocation_fail()
	} else {
    
    
		//注意我这里跟书本的伪代码不一样,因为我认为一开始新建的默认为0,而只要被引用了才改变refCnt
		obj.refCnt = 0
		return obj
	}
	return nil
}
  • pickupChunkFunction: Find whether there is enough space in the free list. Returns an object pointer if found, otherwise nil. This function can also divide the remaining space and return it to the free list.
func pickupChunk(no string, size int, list *FreeLinkedList) *Object {
    
    
	current := list.head
	for current != nil {
    
    
		var object interface{
    
    }
		temp := current.data
		object = temp
		if ms, ok := object.(*Object); ok {
    
    
			oldNo, allocate_size := ms.getInterface()
			if oldNo != "head" {
    
    
				if allocate_size == size {
    
    
					list.deleteNode(object)
					return &Object{
    
    no: no, data: make([]byte, size)}
				} else if allocate_size > size {
    
    
					list.deleteNode(object)
					remainingChunk := &Object{
    
    
						no:   oldNo,
						data: make([]byte, allocate_size-size),
					}
					list.insertAtEnd(remainingChunk)
					return &Object{
    
    no: no, data: make([]byte, size)}
				} else {
    
    
					allocation_fail()
				}
			}
		}
		current = current.next
	}
	return nil
}
  • mergeChunkFunction: It will traverse all objects in the free list and merge them into a large free block. The merged block is added to the end of the linked list.
func mergeChunk(list *FreeLinkedList) {
    
    
	current := list.head
	var totalSize int = 0
	for current != nil {
    
    
		var object interface{
    
    }
		temp := current.data
		object = temp
		if ms, ok := object.(*Object); ok {
    
    
			//allocate_size可分配的
			oldNo, size := ms.getInterface()
			if oldNo != "head" {
    
    
				list.deleteNode(object)
				totalSize += size
			}
		}
		current = current.next
	}
	newNode := &Object{
    
    no: "No.0", data: make([]byte, totalSize)}
	list.insertAtEnd(newNode)
	list.printLinkedList()
}

Note :

The code also involves the processing of the reference count (refCnt) of the object. When the object is newly created, its reference count is initialized to 0; when the object is referenced, its reference count will increase; when the object is no longer referenced , its reference count is decremented.
When an object's reference count reaches 0, it is ready to be recycled.
Additionally, when an object is recycled, its child objects are also recursively recycled.

generate data and executecreateData.go

Use the various functions defined above to simulate the process of memory allocation.

Specifically, a function Executenamed , which accepts a parameter BASE_SPACEindicating the initial space size that can be allocated, and then calls InitDatathe function to initialize the free block list. Then, it calls Example1the function to simulate the process of memory allocation, and finally calls mergeChunkthe function to merge the released free blocks to reduce memory fragmentation.

package PKG

import "fmt"

//书本图3.2的例子
//Num 数量,Size 总需要的空间
var Num int = 3
var Size int = 6
var heap []*Object

func Example1(Num, Size int, list *FreeLinkedList) {
    
    
	avgSize := Size / Num
	root := &Object{
    
    no: "root", refCnt: 1, data: make([]byte, 0)}
	for c, ch := 'A', 'A'+rune(Num); c < ch; c++ {
    
    
		heap = append(heap, newObject(string(c), avgSize, list))
	}
	//root->A
	root.AddRef(heap[0])
	//root->C
	root.AddRef(heap[2])
	//A->B
	heap[0].AddRef(heap[1])
	fmt.Println("创建书本图3.2(a)中update_prt()函数执行时的情况")
	fmt.Println(root)
	fmt.Println(heap[0])
	fmt.Println(heap[1])
	fmt.Println(heap[2])
	//让A->B => A->C
	heap[0].updatePtr(heap[1], heap[2], list)
	fmt.Println("最终结果显示正确,执行图3.2(b)的结果")
	fmt.Println(root)
	fmt.Printf("%p", heap[0])
	fmt.Println(heap[0])
	fmt.Printf("%p", heap[1])
	fmt.Println(heap[1])
	fmt.Printf("%p", heap[2])
	fmt.Println(heap[2])
}

//BASE_SPACE:初始可分配空间大小,以一个链表节点的形式出场
func InitData(BASE_SPACE int) *FreeLinkedList {
    
    
	head := &Node{
    
    data: &Object{
    
    no: "head"}}
	node0 := &Object{
    
    no: "No.0", data: make([]byte, 10)}
	list := &FreeLinkedList{
    
    head: head}
	list.insertAtEnd(node0)
	list.printLinkedList()
	return list
}

func Execute(BASE_SPACE int) {
    
    
	list := InitData(BASE_SPACE)
	Example1(Num, Size, list)
	list.printLinkedList()
	mergeChunk(list)
}

In Example1the function , the code first calculates the average size of each chunk, then loops through to create multiple chunks and adds them to a heapslice called . Next, the code uses AddRefthe function to establish the relationship between pointers, simulating the reference relationship between objects in memory. Specifically, the code first rootpoints to the first block heap[0], then heap[0]points to the second block heap[1], and finally rootpoints to the third block heap[2],

It is (a) in the figure below .
insert image description here

Next, the code calls updatePtrthe function to modify what the pointer points to, simulating the movement of the object in memory. Specifically, the code heap[0]moves the object pointed to from from heap[1]to heap[2], so heap[0]should point to heap[2]instead heap[1]of . Finally, the code prints out the status of each block to show the process of memory allocation and pointer movement, which is (B) in the above figure .

Finally, the code calls mergeChunkthe function to coalesce freed free blocks to reduce memory fragmentation. Specifically, the code uses a loop to traverse the entire free block list, accumulates the size of each free block, and merges them into a large block. Finally, the code inserts this chunk at the end of the list and prints out the state of the list.

Results of themain.go

package main

import (
	L "GCByGo/ReferenceCount/achieve/Considered/PKG"
)

func main() {
    
    
	L.Execute(10)
}

Explain the result:

insert image description here

hateful! Got slapped in the face ( how fast can 13 seconds be? )

insert image description here

At the beginning, the space that can be allocated in the free list is displayed. You can see that [0 0 0 0 ....]this data actually represents the domain, but I actually created an additional childrenslice to store the reference relationship, so the domain itself has no meaning.

&{head 0 [] []}->&{No.0 0 [0 0 0 0 0 0 0 0 0 0] []}->nil

Then there is 3.2(a)an example of creating a book map, which follows the following structure:

&{value of the name counter field[referenced address]}

Should be very intuitive, right?

&{root 1 [] [0xc000100230 0xc000100370]}
&{A 1 [0 0] [0xc0001002d0]}
&{B 1 [0 0] []}
&{C 1 [0 0] []}

Then I cleared B and put him in the free list

B has been reclaimed

The final result is correct, and 3.2(b)the results of the execution graph, in which 0xc000100230these values ​​with unclear meanings are addresses, can be combined with the above results to know that our A points to the address of B at the beginning, and 0xc0001002d0later changes to the address of C 0xc000100370, and changes accordingly is the value of the counter

&{root 1 [] [0xc000100230 0xc000100370]}
0xc000100230&{A 1 [0 0] [0xc000100370]}
0xc0001002d0&{B 0 [0 0] []}
0xc000100370&{C 2 [0 0] []}

Look back at this picture again:
insert image description here
let B enter the free list:

&{head 0 [] []}->&{No.0 0 [0 0 0 0] []}->&{B 0 [0 0] []}->nil

However, following the principle of not wasting, I merged the B blocks that were merged into the free list, so I got the following result.

&{head 0 [] []}->&{No.0 0 [0 0 0 0 0 0] []}->nil

Guess you like

Origin blog.csdn.net/kokool/article/details/130458997