优化一个已有的 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表,可以看到,往表中插入一条记录有两种方式,一种是直接传入一个结构体,一种是传入结构体指针。这两种方式的主要区别在于,当传入的是结构体类型时,会先创建该结构体的一个副本,而如果传入的是指针,则在创建记录时直接使用的是指针指向的结构体,从而避免了数据的复制,减少内存的使用量。

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记