Golang中for循环的性能优化探讨
Golang中for循环的性能优化探讨 你好,如果不介意的话,我想了解一下为什么 Go 语言的 “for” 循环比较慢。我尝试对一些简单的 “for” 循环进行基准测试,比较了 Go 默认编译器、TinyGo 和 Graal Native Image 的性能,结果非常令人失望。即使在我关闭了垃圾回收器之后,TinyGo 和 Graal 的表现非常接近,而默认的 Go 编译器则相差甚远。Go 编译器到底有没有对循环进行任何优化呢?
顺便提一下,TinyGo 在调用 C 代码(CGO)方面也表现得更好。
Dean_Davidson:
Rust 可能是更适合这项工作的工具。不过,可以看看这个:
谢谢,在我看来 Rust 对心理健康不太好。
更多关于Golang中for循环的性能优化探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我并不是在比较低级语言与低级语言,而是在比较高级语言与高级语言,并尝试不同的编译器,请不要对此感到不快,谢谢。
从Go调用C函数比从Go调用Go慢281倍,而从TinyGo调用C函数比从Go调用Go快107倍。我希望随着时间的推移,Go编译器能有很多改进。
Dean_Davidson: 这似乎与你的发现一致。听起来答案可能是“使用TinyGo”。它缺少哪些你需要的Go语言特性?
谢谢,我决定使用它了。至于TinyGo缺少什么,有些功能可能无法按预期工作。
Dean_Davidson: 对于你正在尝试构建的项目。
游戏引擎。我希望Go和C语言对此来说足够好。
游戏引擎。我希望 Go 和 C 语言对此足够胜任。
Rust 可能是更适合这项工作的工具。话虽如此,可以看看:
Ebitengine - 一个极其简单的 Go 语言 2D 游戏引擎
Ebitengine 是一个用于 Go 编程语言的开源游戏引擎。Ebitengine 简单的 API 允许你快速轻松地开发可以跨多个平台部署的 2D 游戏。
我怎么会觉得是针对我呢?Go 是一种工具;我在某些适合它的任务中使用它,在其他任务中则不用。根据你所说的,我是在质疑它是否是你应该使用的正确工具,并提出了一些替代方案。我还建议你如何改进你的问题,以便可能获得更有用的回复。
我重申一下:如果你想获得具体的反馈,请发布具体的基准测试。就目前而言,你的帖子很模糊,没有包含任何代码,因此很可能只会得到模糊的回应。话虽如此,你甚至可能没有对你认为正在测试的内容进行基准测试。看看这些链接:
为什么 Golang 的 for 循环比 Python 的 for 循环慢?
https://www.reddit.com/r/golang/comments/2p46ix/golang_simple_loop_slower_than_java/
众所周知,合成基准测试很难做到正确,这也是人们往往更倾向于真实场景的原因。我对在 TinyGo 中调用 C 不是特别熟悉,但我的理解是,它的开销确实更小:
https://aykevl.nl/2021/11/cgo-tinygo/
并且来自 TinyGo 常见问题解答:
标准的 Go 编译器为 CGo 调用做了一些特殊处理。这是必要的,因为只有 Go 代码可以使用(较小的)Go 栈,而 C 代码需要一个更大的栈。如果新编译器能确保栈足够大以容纳 C,就可以避免这个限制,从而大大减少 C ↔ Go 调用的开销。
这似乎与你的发现一致。听起来答案可能是“使用 TinyGo”。它缺少哪些你需要的 Go 特性?在语言设计中,几乎每件事都是一系列权衡。再次强调——我会质疑 Go 为你正在尝试构建的项目所做的权衡(易用性、goroutine 的简单并发、编译速度、垃圾回收、强大的标准库、工具、生态系统等)是否正确。
你好,Dean,忘掉我之前说的吧。我尝试了更多纯粹的复杂“for”循环(这些循环无法被编译器优化忽略),以确保循环确实在执行。结果发现,Graal Native Image 比 PureGo 和 TinyGo 慢得多,而 TinyGo 稍微快一点:
TinyGo : 432.148669ms 443.742183ms 440.125731ms
Go : 485.346782ms 487.894507ms 477.667357ms
GraalNM: 961.963131ms 974.069681ms 957.945913ms
TinyGo 确实做了一些优化,但遗憾的是它不支持最新 Go 版本的所有功能。有没有一些可以传递给 Go 编译器以进行优化的标志呢?
编辑:在 C 语言中进行了相同的测试并从 Go 调用后,TinyGo 表现得更好得多,我认为 TinyGo 调用 C 的效率更高:
Go : 445.494291ms 446.961961ms 442.149793ms
TinyGo : 85.634284ms 61.546723ms 88.512513ms
在Go语言中,for循环的性能通常与编译器的优化策略、代码生成方式以及运行时环境有关。标准Go编译器(gc)确实会对循环进行优化,但可能不如TinyGo或Graal Native Image那样激进,特别是在特定场景下如嵌入式系统或AOT编译。以下是一些关键点,结合示例代码说明:
-
循环优化:Go编译器会应用常见的循环优化,如循环展开(loop unrolling)和边界检查消除(bounds check elimination)。例如,在遍历切片时,编译器可能优化掉索引的边界检查:
func sumSlice(s []int) int { total := 0 for i := 0; i < len(s); i++ { total += s[i] // 编译器可能消除边界检查 } return total }使用
go build -gcflags="-d=ssa/check_bce/debug=1"可以查看边界检查消除情况。 -
编译器差异:TinyGo专注于生成小型、高效的代码,适用于资源受限环境,它可能采用更积极的优化策略。Graal Native Image通过AOT编译和逃逸分析来提升性能。标准Go编译器则平衡了编译速度、可调试性和性能,这可能在某些循环基准测试中表现不同。
-
性能基准测试示例:以下是一个简单的基准测试,展示如何测试循环性能。确保在真实环境中运行,避免微基准测试的陷阱:
package main import ( "testing" ) func BenchmarkLoop(b *testing.B) { slice := make([]int, 1000) for i := range slice { slice[i] = i } b.ResetTimer() for n := 0; n < b.N; n++ { total := 0 for _, v := range slice { total += v } } }运行
go test -bench=. -benchmem查看结果。 -
CGO性能:TinyGo在CGO调用上可能更优,因为它减少了运行时开销。标准Go的CGO涉及上下文切换和垃圾回收影响,可能导致性能下降。例如:
// #include <stdio.h> import "C" func callC() { C.puts(C.CString("Hello")) // TinyGo可能优化此调用 }
总体而言,Go编译器的循环优化是存在的,但针对不同使用场景(如默认编译、嵌入式或AOT),性能表现会有差异。建议根据具体应用场景选择工具链,并通过基准测试验证优化效果。



