<code style="letter-spacing: 1px; word-spacing: 3px; text-align: left; font-size: 14px; overflow-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin-right: 2px; margin-left: 2px; font-family: " operator mono consolas monaco menlo monospace word-break: break-all background: rgba><span style="font-size: 15px;">while</span>
while、<span style="font-size: 15px;">do...while</span>
<span style="letter-spacing: 1px; word-spacing: 3px; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, " pingfang sc cambria cochin georgia times new roman serif text-align: left font-size:>do...while</span>
等循环控制语法,而仅保留了一种语句,即 for 循环。
for i := 0; i < n; i++ { ... ... }
<span style="font-size: 15px;">for range</span>
但是,经典的三段式循环语句,需要获取迭代对象的长度 n。鉴于此,为了更方便 Go 开发者对复合数据类型进行迭代,例如 array、slice、channel、map,Go 提供了 for 循环的变体,即 for range
循环。
副本复制问题
func main() { var a = [5]int{1, 2, 3, 4, 5} var r [5]int fmt.Println("original a =", a) for i, v := range a { if i == 0 { a[1] = 12 a[2] = 13 } r[i] = v } fmt.Println("after for range loop, r =", r) fmt.Println("after for range loop, a =", a) }range 在带来便利的同时,也给 Go 初学者带来了一些麻烦。因为使用者需要明白一点:for range 中,参与循环表达式的只是对象的副本。
你认为这段代码会输出以下结果吗?
original a = [1 2 3 4 5] after for range loop, r = [1 12 13 4 5] after for range loop, a = [1 12 13 4 5]
但是,实际输出是
original a = [1 2 3 4 5] after for range loop, r = [1 2 3 4 5] after for range loop, a = [1 12 13 4 5]
为什么会这样?原因是参与 for range 循环是 range 表达式的副本。也就是说,在上面的例子中,实际上参与循环的是 a 的副本,而不是真正的 a。
为了让大家更容易理解,我们把上面例子中的 for range 循环改写成等效的伪代码形式。
for i, v := range ac { //ac is a value copy of a if i == 0 { a[1] = 12 a[2] = 13 } r[i] = v }
ac 是 Go 临时分配的连续字节序列,与 a 根本不是同一块内存空间。因此,无论 a 如何修改,它参与循环的副本 ac 仍然保持原始值,因此从 ac 中取出的 v 也依然是 a 的原始值,而不是修改后的值。
那么,问题来了,既然 for range 使用的是副本数据,那 for range 会比经典的 for 循环消耗更多的资源并且性能更差吗?
性能对比
基于副本复制问题,我们先使用基准示例来验证一下:对于大型数组,for range 是否一定比经典的 for 循环运行得慢?
package main import "testing" func BenchmarkClassicForLoopIntArray(b *testing.B) { b.ReportAllocs() var arr [100000]int for i := 0; i < b.N; i++ { for j := 0; j < len(arr); j++ { arr[j] = j } } } func BenchmarkForRangeIntArray(b *testing.B) { b.ReportAllocs() var arr [100000]int for i := 0; i < b.N; i++ { for j, v := range arr { arr[j] = j _ = v } } }
在这个例子中,我们使用 for 循环和 for range 分别遍历一个包含 10 万个 int 类型元素的数组。让我们看看基准测试的结果
$ go test -bench . forRange1_test.go goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz BenchmarkClassicForLoopIntArray-8 47404 25486 ns/op 0 B/op 0 allocs/op BenchmarkForRangeIntArray-8 37142 31691 ns/op 0 B/op 0 allocs/op PASS ok command-line-arguments 2.978s
从输出结果可以看出,for range 的确会稍劣于 for 循环,当然这其中包含了编译器级别优化的结果(通常是静态单赋值,或者 SSA 链接)。
让我们关闭优化开关,再次运行压力测试。
$ go test -c -gcflags '-N -l' . -o forRange1.test $ ./forRange1.test -test.bench . goos: darwin goarch: amd64 pkg: workspace/example/forRange cpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz BenchmarkClassicForLoopIntArray-8 6734 175319 ns/op 0 B/op 0 allocs/op BenchmarkForRangeIntArray-8 5178 242977 ns/op 0 B/op 0 allocs/op PASS
当没有编译器优化时,两种循环的性能都明显下降, for range 下降得更为明显,性能也更加比经典 for 循环差。
遍历结构体数组
上述性能测试中,我们的遍历对象类型是 int 值的数组,如果我们将 int 元素改为结构体会怎么样?for 和 for range 循环各自表现又会如何?
package main import "testing" type U5 struct { a, b, c, d, e int } type U4 struct { a, b, c, d int } type U3 struct { b, c, d int } type U2 struct { c, d int } type U1 struct { d int } func BenchmarkClassicForLoopLargeStructArrayU5(b *testing.B) { b.ReportAllocs() var arr [100000]U5 for i := 0; i < b.N; i++ { for j := 0; j < len(arr)-1; j++ { arr[j].d = j } } } func BenchmarkClassicForLoopLargeStructArrayU4(b *testing.B) { b.ReportAllocs() var arr [100000]U4 for i := 0; i < b.N; i++ { for j := 0; j < len(arr)-1; j++ { arr[j].d = j } } } func BenchmarkClassicForLoopLargeStructArrayU3(b *testing.B) { b.ReportAllocs() var arr [100000]U3 for i := 0; i < b.N; i++ { for j := 0; j < len(arr)-1; j++ { arr[j].d = j } } } func BenchmarkClassicForLoopLargeStructArrayU2(b *testing.B) { b.ReportAllocs() var arr [100000]U2 for i := 0; i < b.N; i++ { for j := 0; j < len(arr)-1; j++ { arr[j].d = j } } } func BenchmarkClassicForLoopLargeStructArrayU1(b *testing.B) { b.ReportAllocs() var arr [100000]U1 for i := 0; i < b.N; i++ { for j := 0; j < len(arr)-1; j++ { arr[j].d = j } } } func BenchmarkForRangeLargeStructArrayU5(b *testing.B) { b.ReportAllocs() var arr [100000]U5 for i := 0; i < b.N; i++ { for j, v := range arr { arr[j].d = j _ = v } } } func BenchmarkForRangeLargeStructArrayU4(b *testing.B) { b.ReportAllocs() var arr [100000]U4 for i := 0; i < b.N; i++ { for j, v := range arr { arr[j].d = j _ = v } } } func BenchmarkForRangeLargeStructArrayU3(b *testing.B) { b.ReportAllocs() var arr [100000]U3 for i := 0; i < b.N; i++ { for j, v := range arr { arr[j].d = j _ = v } } } func BenchmarkForRangeLargeStructArrayU2(b *testing.B) { b.ReportAllocs() var arr [100000]U2 for i := 0; i < b.N; i++ { for j, v := range arr { arr[j].d = j _ = v } } } func BenchmarkForRangeLargeStructArrayU1(b *testing.B) { b.ReportAllocs() var arr [100000]U1 for i := 0; i < b.N; i++ { for j, v := range arr { arr[j].d = j _ = v } } }
在这个例子中,我们定义了 5 种类型的结构体:U1~U5,它们的区别在于包含的 int 类型字段的数量。
性能测试结果如下
$ go test -bench . forRange2_test.go goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz BenchmarkClassicForLoopLargeStructArrayU5-8 44540 26227 ns/op 0 B/op 0 allocs/op BenchmarkClassicForLoopLargeStructArrayU4-8 45906 26312 ns/op 0 B/op 0 allocs/op BenchmarkClassicForLoopLargeStructArrayU3-8 43315 27400 ns/op 0 B/op 0 allocs/op BenchmarkClassicForLoopLargeStructArrayU2-8 44605 26313 ns/op 0 B/op 0 allocs/op BenchmarkClassicForLoopLargeStructArrayU1-8 45752 26110 ns/op 0 B/op 0 allocs/op BenchmarkForRangeLargeStructArrayU5-8 3072 388651 ns/op 0 B/op 0 allocs/op BenchmarkForRangeLargeStructArrayU4-8 4605 261329 ns/op 0 B/op 0 allocs/op BenchmarkForRangeLargeStructArrayU3-8 5857 182565 ns/op 0 B/op 0 allocs/op BenchmarkForRangeLargeStructArrayU2-8 10000 108391 ns/op 0 B/op 0 allocs/op BenchmarkForRangeLargeStructArrayU1-8 36333 32346 ns/op 0 B/op 0 allocs/op PASS ok command-line-arguments 16.160s
我们看到一个现象:不管是什么类型的结构体元素数组,经典的 for 循环遍历的性能比较一致,但是 for range 的遍历性能会随着结构字段数量的增加而降低。
带着疑惑,发现了一个与这个问题相关的 issue:cmd/compile: optimize large structs:https://github.com/golang/go/issues/24416。这个 issue 大致是说:如果一个结构体类型有超过一定数量的字段(或一些其他条件),就会将该类型视为 unSSAable。如果 SSA 不可行,那么就无法通过 SSA 优化,这也是造成上述基准测试结果的重要原因。
结论
对于遍历大数组而言, for 循环能比 for range 循环更高效与稳定,这一点在数组元素为结构体类型更加明显。
另外,由于在 Go 中切片的底层都是通过数组来存储数据,尽管有 for range 的副本复制问题,但是切片副本指向的底层数组与原切片是一致的。这意味着,当我们将数组通过切片代替后,不管是通过 for range 或者 for 循环均能得到一致的稳定的遍历性能。
以上是Go 处理大数组:使用 for range 还是 for 循环?的详细内容。更多信息请关注PHP中文网其他相关文章!

Gooffersrobustfeaturesforsecurecoding,butdevelopersmustimplementsecuritybestpracticeseffectively.1)UseGo'scryptopackageforsecuredatahandling.2)Manageconcurrencywithsynchronizationprimitivestopreventraceconditions.3)SanitizeexternalinputstoavoidSQLinj

Go的错误接口定义为typeerrorinterface{Error()string},允许任何实现Error()方法的类型被视为错误。使用步骤如下:1.基本检查和记录错误,例如iferr!=nil{log.Printf("Anerroroccurred:%v",err)return}。2.创建自定义错误类型以提供更多信息,如typeMyErrorstruct{MsgstringDetailstring}。3.使用错误包装(自Go1.13起)来添加上下文而不丢失原始错误信息,

对效率的Handleerrorsinconcurrentgopragrs,UsechannelstocommunicateErrors,EmparterRorwatchers,InsterTimeouts,UsebufferedChannels和Provideclearrormessages.1)USEchannelelStopassErstopassErrorsErtopassErrorsErrorsFromGoroutInestotheStothemainfunction.2)

在Go语言中,接口的实现是通过隐式的方式进行的。1)隐式实现:类型只要包含接口定义的所有方法,就自动满足该接口。2)空接口:interface{}类型所有类型都实现,适度使用可避免类型安全问题。3)接口隔离:设计小而专注的接口,提高代码的可维护性和重用性。4)测试:接口有助于通过模拟依赖进行单元测试。5)错误处理:通过接口可以统一处理错误。

go'sinterfacesareimpliclyimplysed,与Javaandc#wheRequireexplitiCimplation.1)Ingo,AnyTypewithTheRequiredMethodSautSautsautautapitymethodimimplementalyimimplementsaninternItherninternionterface,callingingSimplicity andficityity.2)

Toensureinitfunctionsareeffectiveandmaintainable:1)Minimizesideeffectsbyreturningvaluesinsteadofmodifyingglobalstate,2)Ensureidempotencytohandlemultiplecallssafely,and3)Breakdowncomplexinitializationintosmaller,focusedfunctionstoenhancemodularityandm

goisidealforbeginnersandsubableforforcloudnetworkservicesduetoitssimplicity,效率和concurrencyFeatures.1)installgromtheofficialwebsitealwebsiteandverifywith'.2)

开发者应遵循以下最佳实践:1.谨慎管理goroutines以防止资源泄漏;2.使用通道进行同步,但避免过度使用;3.在并发程序中显式处理错误;4.了解GOMAXPROCS以优化性能。这些实践对于高效和稳健的软件开发至关重要,因为它们确保了资源的有效管理、同步的正确实现、错误的适当处理以及性能的优化,从而提升软件的效率和可维护性。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

Atom编辑器mac版下载
最流行的的开源编辑器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

Dreamweaver Mac版
视觉化网页开发工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

WebStorm Mac版
好用的JavaScript开发工具