首页 > 其他分享 >高性能 Go 的 6 个技巧 — Go 高级主题

高性能 Go 的 6 个技巧 — Go 高级主题

时间:2023-05-31 20:34:05浏览次数:65  
标签:函数 示例 性能 go 高性能 func Go 技巧

本文旨在讨论6个提示,这些提示可以帮助诊断和修复Go应用程序中的性能问题。

image.png

基准测试:

在Go中编写有效的基准测试对于了解代码性能至关重要。可以通过将文件命名为“_test.go”,并使用testing包的Benchmark函数来创建基准测试。以下是一个示例:

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

func BenchmarkFibonacci(b *testing.B) {
	for n := 0; n < b.N; n++ {
		fibonacci(20)
	}
}

在这个例子中,我们对计算第20个斐波那契数所需的时间进行基准测试。BenchmarkFibonacci函数运行fibonacci函数b.N次,其中b.N是由testing包设置的一个值,以提供具有统计意义的结果。

为了解释基准测试结果,我们可以在终端中运行go test -bench=. -benchmem命令,它会执行当前目录中的所有基准测试,并打印内存分配统计信息。-bench标志用于指定匹配基准测试名称的正则表达式,.将匹配当前目录中的所有基准测试。-benchmem标志将连同计时结果一起打印内存分配统计信息。

性能分析

Go提供了内置的性能分析工具,可以帮助您了解代码的运行情况。最常用的性能分析工具是CPU分析器,可以通过在go test命令中添加-cpuprofile标志来启用。以下是一个示例:

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

func TestFibonacci(t *testing.T) {
	result := fibonacci(20)
	expected := 6765
	if result != expected {
		t.Errorf("Expected %d, but got %d", expected, result)
	}
}

func BenchmarkFibonacci(b *testing.B) {
	for n := 0; n < b.N; n++ {
		fibonacci(20)
	}
}

func ExampleFibonacci() {
	result := fibonacci(20)
	fmt.Println(result)
	// Output: 6765
}

第一个函数“TestFibonacci”是一个简单的单元测试,用于检查fibonacci函数是否正确返回斐波那契数列中的第20个数字。

“fibonacci”函数是斐波那契数列的递归实现,用于计算数列中第n个数字。

“BenchmarkFibonacci”函数是一个基准测试,运行“fibonacci”函数20次并测量执行时间。

“ExampleFibonacci”函数是一个示例,使用“fibonacci”函数打印斐波那契数列中的第20个数字,并检查其是否等于预期值6765。

要启用性能分析,我们可以在go test命令中使用-cpuprofile标志将性能分析结果输出到名为prof.out的文件中。以下命令可用于运行测试并生成性能分析数据:

go test -cpuprofile=prof.out

运行测试后,我们可以使用go tool pprof命令来分析性能分析数据。可以使用以下命令启动pprof工具的交互式shell:

go tool pprof prof.out

这将打开pprof的交互式shell,我们可以在其中输入各种命令来分析性能分析数据。例如,我们可以使用top命令显示消耗CPU时间最多的函数:

(pprof) top

这将显示按CPU时间排序的消耗CPU时间最多的函数列表。在这个例子中,我们应该会看到fibonacci函数位于列表的顶部,因为它在基准测试期间消耗了最多的CPU时间。

我们还可以使用web命令以图形格式显示性能分析数据,使用list命令显示带有性能分析数据的源代码。

性能分析是一个强大的工具,可以帮助我们识别代码中的性能瓶颈。通过使用-cpuprofile标志和go tool pprof,我们可以轻松生成和分析Go测试和应用程序的性能分析数据。

编译优化

Go编译器执行多项优化,包括内联、逃逸分析和死代码消除。内联是将函数调用替换为函数体的过程,通过减少函数调用开销来提高性能。逃逸分析是确定变量是否被取地址的过程,它可以帮助编译器将变量分配在栈上而不是堆上。死代码消除是删除永远不会执行的代码的过程。

内联优化

// Without inlining
func add(a, b int) int {
	return a + b
}
func main() {
	result := add(3, 4)
	fmt.Println(result)
}

// With inlining
func main() {
	result := 3 + 4
	fmt.Println(result)
}

在第一个示例中,使用参数 3 和 4 调用了 add 函数,这会导致函数调用开销。而在第二个示例中,函数调用被替换为实际的函数代码,从而加快了执行速度。

逃逸分析

func main() {
	var a int
	b := &a
	fmt.Println(b)
}

在这个例子中,变量 a 被分配在栈上,因为它的地址没有被取出。然而,变量 b 被分配在堆上,因为它的地址被使用了 & 操作符取出。

逃逸分析的更多内容

type User struct {
	name  string
	email string
}

func createUser(name string, email string) *User {
	u := User{name: name, email: email}
	return &u
}

在 createUser 函数中,创建了一个新的 User 并返回其地址。注意,由于返回了 User 值的地址,所以它被分配在栈上,因此不会逃逸到堆上。

如果我们在返回之前添加了一个获取 User 值地址的行:

func createUser(name string, email string) *User {
	u := User{name: name, email: email}
	up := &u
	return up
}

现在, User 值的地址被获取并存储在一个变量中,然后返回。这导致该值逃逸到堆上而不是分配在栈上。

逃逸分析很重要,因为堆分配比栈分配更昂贵,所以减少堆分配可以提高性能。

死代码消除

func main() {
	if false {
		fmt.Println("This code is dead")
	}
	fmt.Println("This code is alive")
}

在这个例子中,if语句内的代码永远不会被执行,所以在编译器进行死代码消除时会被删除。

理解执行跟踪器

Go语言中的执行跟踪器提供了关于程序运行情况的详细信息,包括堆栈跟踪、goroutine阻塞等。以下是如何使用它的示例:

package main

import (
	"fmt"
	"os"
	"runtime/trace"
)

func main() {
	f, err := os.Create("trace.out")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	err = trace.Start(f)
	if err != nil {
		panic(err)
	}
	defer trace.Stop()

	fmt.Println("Hello, World!")
}

在这个示例中,我们创建了一个跟踪文件,开始跟踪,并停止跟踪。当程序运行时,跟踪数据将被写入到名为trace.out的文件中。然后,您可以分析这些跟踪数据,以更好地理解程序的运行情况。

内存管理和垃圾回收调优

在Go语言中,垃圾回收是自动进行的,并由运行时管理。然而,我们可以通过一些方式来调优垃圾回收器以提高性能。以下是如何设置一些垃圾回收器选项的示例:

package main

import (
	"fmt"
	"runtime"
	"runtime/debug"
)

func main() {
	// Set the maximum number of CPUs to use
	runtime.GOMAXPROCS(2)

	// Set the minimum heap size to 1GB
	runtime.MemProfileRate = 1 << 30

	// Set the garbage collection percentage to 50%
	debug.SetGCPercent(50)

	fmt.Println("Hello, World!")
}

在这个示例中,我们设置了最大CPU使用数量、最小堆大小和垃圾回收百分比。这些设置可以根据程序的需求进行调整,以提高性能。

并发:

Go语言通过goroutines和channels提供了内置的并发支持。然而,为了避免出现竞态条件和死锁等问题,正确使用这些特性非常重要。以下是如何使用channels在goroutines之间进行安全通信的示例:

package solution

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func() {
		time.Sleep(1 * time.Second)
		ch <- 1
	}()
	select {
	case <-ch:
		fmt.Println("Received message")
	case <-time.After(2 * time.Second):
		fmt.Println("Timed out")
	}
}

make(chan int)语句创建了一个用于在两个goroutines之间通信整数值的channel。

第一个goroutine使用go func() {...}()语句创建,它在休眠1秒后向channel ch发送一个值为1的数据。这意味着在1秒后,ch通道中将有一个值为1的数据。

第二个goroutine使用select语句创建,它等待ch通道的通信。如果从通道接收到一个值,就会打印出"Received message"的消息。如果在2秒内没有接收到值,就会打印出"Timed out"的消息。

因此,尽管select语句和第一个goroutine之间没有明确的通信,但仍然通过共享的ch通道进行通信。

最后:

如果您喜欢这篇文章,请关注或订阅以及时接收高质量的内容。感谢您的支持 ;)

标签:函数,示例,性能,go,高性能,func,Go,技巧
From: https://blog.51cto.com/slagga/6389386

相关文章

  • golang之recover
    recover是什么golang的recover是一个内置函数,用于在发生panic时恢复程序的控制流。当程序发生panic时,程序会停止执行当前的函数,并向上层函数传递panic,直到被recover函数捕获。recover函数必须在defer语句中调用,否则无法捕获panic。如果没有发生panic或者没有被recover函数捕获,程序......
  • Go-Map相关
    Go中map默认不安全的,也实现了并发安全的对象:sync.Map并发不安全不安全是因为源码中没有实现读写分离。进行了判断异常:在哈希表写操作时,会将哈希表的标志位 hashWriting 设置为1,以表明当前正在执行写操作。当其他协程执行哈希表的读操作时,会根据当前的标志位判断是否能够......
  • 1008.Django项目用户功能之docker
    docker跟virtualbox一样:是一个虚拟软件,可以创建多个程序的运行环境。docker与virtualbox的差别:docker不会虚拟出自己的内核,而是直接使用宿主机的内核。为什么要用docker? 集群:分布式相关的环境使用和部署mysql长沙 mysql北京 mysql上海 数据同步,可以相互提供数据服务,而......
  • Google Pixel 4 Android13 刷入Magisk + KernelSU 双root环境
    本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!GooglePixel4Android13刷入Magisk+KernelSU双root环境环境win10Pixel4Android13下载官方rom包......
  • mongodb压缩——snappy、zlib块压缩,btree索引前缀压缩
    MongoDB3.0WiredTigerCompressionandPerformanceOneofthemostexcitingdevelopmentsoverthelifetimeofMongoDBmustbetheinclusionoftheWiredTigerstorageengineinMongoDB3.0.Itsverydesignandcorearchitecturearelegionsaheadofthecurr......
  • golang实现设计模式之抽象工厂模式总结-代码、优缺点、适用场景
    抽象工厂模式也是一种创建型的设计模式,其是在工厂模式的基础上实现更高程度的内聚。我们知道在工厂模式中,一种产品类就需要新建个对应的工厂类生成产品的实例,这会有什么问题呢?虽然工厂模式解决了简单工厂模式不好扩展的问题,实现了OCP,但一种产品就需要新建一个工厂类,比如有10000种......
  • Gorm - 使用gorm时进行执行自定义SQL的几种方式
    1、当只需要执行某个SQL而不需要进行获取返回值时//如果其中有变量,则使用?进行占位,sql:="要执行的SQL"//在Exec方法中在sql后面可以使用多个参数作为占位的补充//例如需要name=?,则写法可以使用util.Db.Exec(sql,"张三").Errorerr:=util.Db.Exec......
  • golang实现设计模式之工厂模式总结-代码、优缺点、适用场景
    工厂模式也是一种创建型模式,它与简单工厂不同的是将实例的创建推迟到具体的工厂类方法中实现,每一种产品生成一个对应的工厂,从而替换掉简单工厂方法模式中那个静态工厂方法。所以在工厂模式中,不同产品就由不同的工厂生产,每次增加产品时,我们就不需要在类似在简单工厂中,在统一的工厂......
  • GO分支循环
    (文章目录)单分支ifcondition{代码块}if5>2{fmt.Println("5greaterthan2")}==注意==:Go语言中,花括号一定要跟着if、for、func等行的最后,否则语法出错。这其实就是为了解决C风格、Java风格之争。condition必须是一个bool类型,在Go中,不能使用其他类型等效......
  • API NEWS | 三个Argo CD API漏洞
    欢迎大家围观小阑精心整理的API安全最新资讯,在这里你能看到最专业、最前沿的API安全技术和产业资讯,我们提供关于全球API安全资讯与信息安全深度观察。本周,我们带来的分享如下:关于三个ArgoCDAPI漏洞的文章Gartner对API安全的看法分布式标识是现代API安全的关键关于三个ArgoCDAPI......