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