Go語(yǔ)言中for和range的性能比較
能GET到的知識(shí)點(diǎn)
什么場(chǎng)景使用for和range
1. 從一個(gè)遍歷開(kāi)始
1.1萬(wàn)能的range遍歷
1.遍歷array/slice/strings
array
package main import "fmt" func main() { var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} for i, v := range UserIDList { fmt.Println(i, v) } }
輸出:
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
slice
package main import "fmt" func main() { var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} var UerSlice = UserIDList[:] for i, v := range UerSlice { fmt.Println(i, v) } }
輸出:
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
字符串
func main(){ var Username = "斑斑磚abc" for i, v := range Username { fmt.Println(i, v) } }
輸出:
0 26001
3 26001
6 30742
9 97
10 98
11 99
range進(jìn)行對(duì)array、slice類(lèi)型遍歷一切都正常,但是到了對(duì)字符串進(jìn)行遍歷時(shí)這里就出問(wèn)題了,出問(wèn)題主要在索引這一塊??梢钥闯鏊饕敲總€(gè)字節(jié)的位置,在go語(yǔ)言中的字符串是UTF-8編碼的字節(jié)序列。而不是單個(gè)的Unicode字符。遇到中文字符時(shí)需要使用多個(gè)字節(jié)表示,英文字符一個(gè)字節(jié)進(jìn)行表示,索引0-3表示了一個(gè)字符及斑
以此完后。
2.遍歷map
func ByMap() { m := map[string]int{ "one": 1, "two": 2, "three": 3, } for k, v := range m { delete(m, "two") m["four"] = 4 fmt.Printf("%v: %v\n", k, v) } }
輸出:
one: 1
four: 4
three: 3
- 和切片不同的是,迭代過(guò)程中,刪除還未迭代到的鍵值對(duì),則該鍵值對(duì)不會(huì)被迭代。
- 在迭代過(guò)程中,如果創(chuàng)建新的鍵值對(duì),那么新增鍵值對(duì),可能被迭代,也可能不會(huì)被迭代。個(gè)人認(rèn)為應(yīng)該是hash的無(wú)序性問(wèn)題
- 針對(duì) nil 字典,迭代次數(shù)為 0
3.遍歷channel
func ByChannel() { ch := make(chan string) go func() { ch <- "a" ch <- "b" ch <- "c" ch <- "d" close(ch) }() time.Sleep(time.Second) for n := range ch { fmt.Println(n) } }
- 針對(duì)于range對(duì)關(guān)閉channel的遍歷,會(huì)直到把元素都讀取完成。
- 但是在for遍歷會(huì)造成阻塞,因?yàn)閒or變量讀取一個(gè)關(guān)閉的管道并不會(huì)進(jìn)行退出,而是一直進(jìn)行等待,但是如果關(guān)閉了會(huì)返回一個(gè)狀態(tài)值可以根據(jù)該狀態(tài)值判斷是否需要操作
2.for和range之間奇怪的問(wèn)題
2.1 無(wú)限遍歷現(xiàn)象
for
c := []int{1, 2, 3} for i := 0; i < len(c); i++ { c = append(c, i) fmt.Println(i) }
輸出:
1
2
3
.
.
.
15096
15097
15098
15099
15100
15101
15102
15103
15104
range
c := []int{1, 2, 3} for _, v := range c { c = append(c, v) fmt.Println(v) }
輸出:
1
2
3
可以看出for循環(huán)一直在永無(wú)止境的進(jìn)行追加元素。 range循環(huán)正常。原因:for循環(huán)的i < len(c)-1
都會(huì)進(jìn)行重新計(jì)算一次,造成了永遠(yuǎn)都不成立。range循環(huán)遍歷在開(kāi)始前只會(huì)計(jì)算一次,如果在循環(huán)進(jìn)行修改也不會(huì)影響正常變量。
2.2 在for和range進(jìn)行修改操作
for
type UserInfo struct { Name string Age int } var UserInfoList = [3]UserInfo{ {Name: "John", Age: 25}, {Name: "Jane", Age: 30}, {Name: "Mike", Age: 28}, } for i := 0; i < len(UserInfoList); i++ { UserInfoList[i].Age += i } fmt.Println(UserInfoList)
輸出:
0
1
2
[{John 25} {Jane 31} {Mike 30}]
range
var UserInfoList = [3]UserInfo{ {Name: "John", Age: 25}, {Name: "Jane", Age: 30}, {Name: "Mike", Age: 28}, } for i, info := range UserInfoList { info.Age += i } fmt.Println(UserInfoList)
輸出:
[{John 25} {Jane 30} {Mike 28}]
可以看出for循環(huán)進(jìn)行修改了成功,但是在range
循環(huán)修改失效,為什么呢?因?yàn)?code>range循環(huán)返回的是對(duì)該值的拷貝,所以修改失效。for循環(huán)修相當(dāng)于進(jìn)行原地修改了。但如果在for循環(huán)里面進(jìn)行賦值修改操作,那么修改也會(huì)進(jìn)行失效 具體如下
var UserInfoList = [3]UserInfo{ {Name: "John", Age: 25}, {Name: "Jane", Age: 30}, {Name: "Mike", Age: 28}, } for i := 0; i < len(UserInfoList); i++ { fmt.Println(i) item := UserInfoList[i] item.Age += i } fmt.Println(UserInfoList)
輸出:
> [{John 25} {Jane 30} {Mike 28}]
3. Benchmark大比拼
主要是針對(duì)大類(lèi)型結(jié)構(gòu)體
type Item struct { id int val [4096]byte }
for_test.go
func BenchmarkForStruct(b *testing.B) { var items [1024]Item for i := 0; i < b.N; i++ { length := len(items) var tmp int for k := 0; k < length; k++ { tmp = items[k].id } _ = tmp } } func BenchmarkRangeStruct(b *testing.B) { var items [1024]Item for i := 0; i < b.N; i++ { var tmp int for _, item := range items { tmp = item.id } _ = tmp } }
goos: windows
goarch: amd64
pkg: article/02fortest
cpu: AMD Ryzen 5 5600G with Radeon Graphics
BenchmarkForStruct-12 2503378 474.8 ns/op 0 B/op 0 allocs/op
BenchmarkRangeStruct-12 4983 232744 ns/op 0 B/op 0 allocs/op
PASS
ok article/02fortest 3.268s
可以看出 for
的性能大約是 range
的 600 倍。
為什么會(huì)產(chǎn)生這么大呢?
上述也說(shuō)過(guò),range遍歷會(huì)對(duì)迭代的值創(chuàng)建一個(gè)拷貝。在占據(jù)占用較大的結(jié)構(gòu)時(shí)每次都需要進(jìn)行做一次拷貝,取申請(qǐng)大約4kb的內(nèi)存,顯然是大可不必的。所以在對(duì)于占據(jù)較大的結(jié)構(gòu)時(shí),應(yīng)該使用for進(jìn)行變量操作。
總結(jié)
如何選擇合適的遍歷,在針對(duì)與測(cè)試場(chǎng)景的情況下,圖便捷可以使用range,畢竟for循環(huán)需要寫(xiě)一堆的條件,初始值等。但是如果遍歷的元素是個(gè)占用大個(gè)內(nèi)存的結(jié)構(gòu)的話(huà),避免使用range進(jìn)行遍歷。且如果需要進(jìn)行修改操作的話(huà)只能用for遍歷來(lái)修改,其實(shí)range也可以進(jìn)行索引遍歷的,在本文為寫(xiě),讀者可以去嘗試一下。
到此這篇關(guān)于Go語(yǔ)言中for和range的性能比較的文章就介紹到這了,更多相關(guān)Go for range內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go程序的init函數(shù)在什么時(shí)候執(zhí)行
在Go語(yǔ)言中,init?函數(shù)是一個(gè)特殊的函數(shù),它用于執(zhí)行程序的初始化任務(wù),本文主要介紹了Go程序的init函數(shù)在什么時(shí)候執(zhí)行,感興趣的可以了解一下2023-10-10Go語(yǔ)言常見(jiàn)錯(cuò)誤之誤用init函數(shù)實(shí)例解析
Go語(yǔ)言中的init函數(shù)為開(kāi)發(fā)者提供了一種在程序正式運(yùn)行前初始化包級(jí)變量的機(jī)制,然而,由于init函數(shù)的特殊性,不當(dāng)?shù)厥褂盟赡芤鹨幌盗袉?wèn)題,本文將深入探討如何有效地使用init函數(shù),列舉常見(jiàn)誤用并提供相應(yīng)的避免策略2024-01-01golang 如何用反射reflect操作結(jié)構(gòu)體
這篇文章主要介紹了golang 用反射reflect操作結(jié)構(gòu)體的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Golang中自定義json序列化時(shí)間格式的示例代碼
Go語(yǔ)言作為一個(gè)由Google開(kāi)發(fā),號(hào)稱(chēng)互聯(lián)網(wǎng)的C語(yǔ)言的語(yǔ)言,自然也對(duì)JSON格式支持很好,下面這篇文章主要介紹了關(guān)于Golang中自定義json序列化時(shí)間格式的相關(guān)內(nèi)容,下面話(huà)不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧2024-08-08GO語(yǔ)言異常處理機(jī)制panic和recover分析
這篇文章主要介紹了GO語(yǔ)言異常處理機(jī)制panic和recover,分析了捕獲運(yùn)行時(shí)發(fā)生錯(cuò)誤的方法,是非常實(shí)用的技巧,需要的朋友可以參考下2014-12-12linux下通過(guò)go語(yǔ)言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過(guò)go語(yǔ)言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實(shí)例分析了Go語(yǔ)言使用linux的系統(tǒng)命令ps來(lái)分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03golang 設(shè)置web請(qǐng)求狀態(tài)碼操作
這篇文章主要介紹了golang 設(shè)置web請(qǐng)求狀態(tài)碼操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12詳解Golang中使用map時(shí)的注意問(wèn)題
Golang中的map是一種數(shù)據(jù)結(jié)構(gòu),它允許你使用鍵值對(duì)的形式存儲(chǔ)和訪(fǎng)問(wèn)數(shù)據(jù),map在Go中是非排序的,提供了高效查找、插入和刪除元素的能力,特別是當(dāng)鍵是不可變類(lèi)型,本文給大家詳細(xì)介紹了Golang中使用map時(shí)的注意問(wèn)題,需要的朋友可以參考下2024-06-06