GC调优.

一.控制内存分配的速度

有效的方法就是控制内存分配的速度.过多的内存分配会导致频繁的垃圾回收,增加CPU的负载.

1.限制Goroutine的数量: 因为每个协程都需要分配空间栈,在GO语言中sync.WaitGroup机制控制协程的数量

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 确保任务完成时减少计数

	fmt.Printf("协程 %d 开始工作\n", id)
	time.Sleep(2 * time.Second) // 模拟耗时任务
	fmt.Printf("协程 %d 完成工作\n", id)
}

func main() {
	var wg sync.WaitGroup

	// 假设有 5 个任务
	numWorkers := 5

	for i := 1; i <= numWorkers; i++ {
		wg.Add(1) // 增加 WaitGroup 的计数
		go worker(i, &wg) // 启动协程执行任务
	}

	// 等待所有协程完成
	wg.Wait()
	fmt.Println("所有任务完成")
}

二.字符串连接优化

在go语言中字符串连接(如使用 + 运算符)操作会频繁导致内存分配和拷贝,特别在循环中.

尽量少使用字符串连接,可以使用strings.Builder 或者bytes.Buffer类型来高效构建字符串.

字符串连接内存消耗大的原因:Go 中,字符串是不可变的(immutable),这意味着每次对字符串进行拼接时,都会创建一个新的字符串,并且将原字符串和新的内容复制到新的内存空间中。这会导致频繁的内存分配和复制操作,特别是在拼接大量字符串时,会产生较大的性能开销.使用 + 拼接多个字符串,每个拼接操作都会产生一个中间结果.

strings.Builder 或者bytes.Buffer消耗小的原因:strings.Builderbytes.Buffer 在内部使用一个动态扩展的内存缓冲区,数据追加时不会立即创建新对象,而是直接修改内部缓冲区。当拼接完成时,内部缓冲区的数据会一次性转换为字符串。这样可以避免在拼接过程中多次复制数据。所有的拼接操作都在缓冲区内进行,避免了中间结果的生成,从而减少了内存使用

bytes.Buffer 处理的是 []byte 类型的字节切片,而 strings.Builder 处理的是字符串。

//使用 strings.Builder 示例
package main

import (
	"fmt"
	"strings"
)

func main() {
	// 使用 strings.Builder 高效拼接字符串
	var builder strings.Builder
	builder.WriteString("Hello")
	builder.WriteString(" ")
	builder.WriteString("World")

	// 获取最终字符串
	result := builder.String()
	fmt.Println(result) // 输出: Hello World
}

//使用 bytes.Builder 示例
package main

import (
	"bytes"
	"fmt"
)

func main() {
	// 使用 bytes.Buffer 高效拼接字符串
	var buffer bytes.Buffer
	buffer.WriteString("Hello")
	buffer.WriteString(" ")
	buffer.WriteString("World")

	// 获取最终字符串
	result := buffer.String()
	fmt.Println(result) // 输出: Hello World
}





三.切片内存分配优化

GO的切片是动态数组,运行时动态增长.但是切片 的扩容可能会导致内存分配和拷贝.可以预先分配足够的内存大小.

切片的容量为 10,当我们追加第 11 个元素时,Go 会将切片的容量加倍,变为 20,并将原有的 10 个元素拷贝到新的内存位置。

例如:

slice := make([]int, 0 ,100)

四.避免过多的Map键对象

在GO中使用Map对象,过多的键会导致map的扫描时间增加从而影响性能.

  • 优化哈希函数:设计高效的哈希函数,减少冲突。
  • 预分配容量:使用 make 函数预分配 map 的容量,避免频繁扩容。
  • 分段存储:将数据分散到多个子 map 中,降低单个 map 的元素数量。
  • 监控性能:使用工具(如 pprof)监控 map 的性能,及时发现问题。

五.变量复用和对象池

优化内存的关键是减少对象的分配.主要采用一下方法实现变量复用和对象池

  • 变量复用:在循环内部避免创建新对象.在循环外面定义变量和重复使用他们.
  • 使用sync.Pool:对于需要频繁创建临时的对象,可以使用sync.Pool来维护对象池.在需要时从对象池中获取,使用完毕后放回.
package main

import (
	"fmt"
	"sync"
)

// 定义一个对象结构体
type Data struct {
	ID   int
	Name string
}

func main() {
	// 创建一个 sync.Pool 来管理 Data 对象
	var pool = sync.Pool{
		// New 函数用于创建新对象,如果池中没有可用对象时
		New: func() interface{} {
			// 创建一个新的 Data 对象
			return &Data{}
		},
	}

	// 获取一个对象,第一次会调用 New 函数
	data1 := pool.Get().(*Data) // 从池中获取对象并断言类型
	data1.ID = 1
	data1.Name = "Object 1"
	fmt.Printf("获取对象: ID=%d, Name=%s\n", data1.ID, data1.Name)

	// 将对象放回池中,等待下次重用
	pool.Put(data1)

	// 获取一个对象,池中已有可用对象
	data2 := pool.Get().(*Data)
	fmt.Printf("获取对象: ID=%d, Name=%s\n", data2.ID, data2.Name)

	// 将对象放回池中
	data2.ID = 2
	data2.Name = "Object 2"
	pool.Put(data2)

	// 获取一个对象,池中已有可用对象
	data3 := pool.Get().(*Data)
	fmt.Printf("获取对象: ID=%d, Name=%s\n", data3.ID, data3.Name)
}

六.增大GOGC的值.就是根据需要调整goGC机制在中设定的GC的内存值(常量)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值