优化一个已有的 Go 程序,提高其性能并减少资源占用 | 青训营

本文将通过几个具体的小案例介绍优化Go程序的一些思路。

性能优化

使用goroutine进行并发编程

goroutine是Go语言中的轻量级线程,它由Go运行时环境管理。与传统的线程相比,goroutine的创建和销毁开销很小,可以高效地并发执行任务。通过关键字"go",可以在Go程序中创建goroutine,它可以在后台执行函数或方法,而不会阻塞主线程。goroutine之间可以通过通道进行通信,实现数据的同步和共享。由于goroutine的特性,Go语言在处理并发任务时非常高效,能够充分利用多核处理器的性能。通过goroutine,开发者可以轻松地编写并发程序,提高程序的性能和响应能力。下面是一个具体的示例。

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	cal1()
	cal2()
}

func cal1() {
	start := time.Now()

	n := 10 // 需要计算的斐波那契数的个数
	results := make(chan int, 4)

	for i := 0; i < n; i++ {
            go func(i int) {
                    fib := fibonacci(i)
                    results <- fib
            }(i)
	}

	for i := 0; i < n; i++ {
		fib := <-results
		fmt.Println(fib)
	}

	elapsed := time.Since(start)
	fmt.Println("程序运行时间:", elapsed)
}

func cal2() {
	start := time.Now()

	n := 10 // 需要计算的斐波那契数的个数

	for i := 0; i < n; i++ {
		fmt.Println(fibonacci(i))
	}

	elapsed := time.Since(start)
	fmt.Println("程序运行时间:", elapsed)
}

func fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return fibonacci(n-1) + fibonacci(n-2)
}

运行结果:

上述代码中,函数cal1和cal2的功能都是计算n个斐波那契数,不同的是,cal1中开启了多个子协程并行计算,即并行计算fibonacci(0),...,fibonacci(9)。从运行结果可以看出,相比cal2串行计算不同的斐波那契数,cal1所需花费的计算时间少于cal2,这说明了goroutine可以有效的优化程序的性能。

使用strings.Builder进行字符串拼接

go 复制代码
package main

import (
	"fmt"
	"strings"
	"time"
)

func Plus(n int, str string) string {
	time.Sleep(1 * time.Millisecond)
	s := ""
	for i := 0; i < n; i++ {
		s += str
	}
	return s
}

func StrBuilder(n int, str string) string {
	time.Sleep(1 * time.Millisecond)
	var builder strings.Builder
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}

func main() {
	start := time.Now()
	Plus(2000, "string")
	elapsed := time.Since(start)
	fmt.Println("Plus运行时间:", elapsed)

	start = time.Now()
	StrBuilder(2000, "string")
	elapsed = time.Since(start)
	fmt.Println("StrBuilder运行时间:", elapsed)
}

运行结果:

从上述代码可以看出,strings.Builder字符串拼接的效率远高于+,这是因为在使用+拼接字符串时,每次都需要分配新的内存空间来存储新的字符串,而strings.Builder底层使用了字节切片([]byte)来存储字符串,并使用高效的扩容机制进行扩容,因此可以避免频繁的内存重新分配,从而提高效率。

内存优化

使用空结构体节省内存

在go语言中,空结构体不占任何内存空间。这使得我们可以在一些场景下利用这一特点对程序的内存占用进行优化。

go 复制代码
m := make(map[int]struct{})
m := make(map[int]bool)

比如,在go语言中,通常使用map实现set,此时由于map的value不需要用到,可以使用空结构体作为占位符。相比将value设置为其它类型的数据如bool,map[int]struct{}这种方式可以有效地减少内存占用。

使用结构体指针节省内存

go 复制代码
type User struct {
	ID uint
	Name string
	Gender string
	Hobby string
}

func main(){
    ...
    db.AutoMigrate(&User{})
    u1 := User{1, "小王", "男", "篮球"}
    db.Create(u1)
    db.Create(&u1)
    ...
}

在这个例子中,我们定义了一个User结构体,对应数据库中的一张users表,可以看到,往表中插入一条记录有两种方式,一种是直接传入一个结构体,一种是传入结构体指针。这两种方式的主要区别在于,当传入的是结构体类型时,会先创建该结构体的一个副本,而如果传入的是指针,则在创建记录时直接使用的是指针指向的结构体,从而避免了数据的复制,减少内存的使用量。

相关推荐
Find24 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记