Preface
The for range statement is a code that is written frequently in business development, and there will be some common pitfalls. After reading this article, you will get less trapped.
Basic usage of for range
range is an iterative traversal method provided by Golang. The operable types include arrays, slices, strings, maps, channels, etc.
1. Iterate through the array
myArray := [3]int{1, 2, 3} for i, ele := range myArray { ("index:%d,element:%d\n", i, ele) ("index:%d,element:%d\n", i, myArray[i]) }
Take the element directly or take it through subscript
2. Traverse slice
mySlice := []string{"I", "am", "peachesTao"} for i, ele := range mySlice { ("index:%d,element:%s\n", i, ele) ("index:%d,element:%s\n", i, mySlice[i]) }
Take the element directly or take it through subscript
3. Traverse string
s:="peachesTao" for i,item := range s { (string(item)) ("index:%d,element:%s\n", i, string(s[i])) }
Take the element directly or take it through subscript
Note: The element in the string in the loop body is actually type byte and needs to be converted to literal characters
4. Traverse map
myMap := map[int]string{1:"Chinese",2:"math",3:"English"} for key,value := range myMap { ("key:%d,value:%s\n", key, value) ("key:%d,value:%s\n", key, myMap[key]) }
Take the element directly or take it through subscript
5. Traverse channel
myChannel := make(chan int) go func() { for i:=0;i<10;i++{ () myChannel <- i } }() go func() { for c := range myChannel { ("value:%d\n", c) } }()
Channel traversal is a loop that reads data from the channel. If there is no data in the channel, it will block and wait, and if the channel has been closed, it will exit the loop.
Difference between for range and for
for range can directly access elements in the target object, and for must be accessed through subscript
for frange can access map and channel objects, but for cannot
for range easy to step on pit
The following example is to add the character "-new" to each element in mySlice
mySlice := []string{"I", "am", "peachesTao"} for _, ele := range mySlice { ele=ele+"-new" } (mySlice)
result:
[I am peachesTao]
When printing mySlice, I found that the element was not updated. Why is this happening?
The reason is that the for range statement will copy a copy of the value of the element in the target object. Modifying the copy obviously cannot affect the original element.
To prove the above conclusion, the memory address of the element is printed before and during the traversal
mySlice := []string{"I", "am", "peachesTao"} ("Memory address of the first element before traversing: %p\n",&mySlice[0]) for _, ele := range mySlice { ele=ele+"-new" ("Memory address of element in traversal: %p\n",&ele) } (mySlice)
result:
Memory address of the first element before traversing: 0xc000054180
Memory address of the second element before traversing: 0xc000054190
The memory address of the third element before traversing: 0xc0000541a0
Memory address of element in traversal: 0xc000010200
Memory address of element in traversal: 0xc000010200
Memory address of element in traversal: 0xc000010200
[I am peachesTao]
Two conclusions can be drawn:
- The memory address of the element in the traversal body has changed, and a copy of the element has been generated. As for the reason for the copy, it will be introduced in the "For Range Underlying Principles" paragraph.
- Only a global element copy variable is generated in the traversal body. Not every element will generate a copy. This feature is also worthy of everyone's attention, otherwise it will be trapped.
For example, traversing mySlice element to generate a mySliceNew of type []*string. You need to use an intermediate variable to get the address of the intermediate variable (or access the element through the subscript) and add mySliceNew. If you directly take the address of the element copy, it will cause all elements in mySliceNew to be the same, as follows:
mySlice := []string{"I", "am", "peachesTao"} var mySliceNew []*string for _, item := range mySlice { itemTemp := item mySliceNew = append(mySliceNew, &itemTemp) //mySliceNew = append(mySliceNew, &item) Incorrect practice}
Going back to the question just now, how can we modify elements in traversal? The answer is to directly access the element in the slice through the subscript to assign it to it, as follows:
mySlice := []string{"I", "am", "peachesTao"} for i, _ := range mySlice { mySlice[i] = mySlice[i]+"-new" } (mySlice)
result:
[I-new am-new peachesTao-new]
You can see that the elements have been modified
Comparison of for range and for performance
We define a structure Item, which contains an id field of type int, and use the for, for range item, and for range index to traverse the structure array respectively. The following is the test code (directly refer to the example in the article "Go high-performance programming", and the following reference has the link address)
type Item struct { id int } func BenchmarkForStruct(b *) { var items [1024]Item for i := 0; i < ; i++ { length := len(items) var tmp int for k := 0; k < length; k++ { tmp = items[k].id } _ = tmp } } func BenchmarkRangeIndexStruct(b *) { var items [1024]Item for i := 0; i < ; i++ { var tmp int for k := range items { tmp = items[k].id } _ = tmp } } func BenchmarkRangeStruct(b *) { var items [1024]Item for i := 0; i < ; i++ { var tmp int for _, item := range items { tmp = } _ = tmp } }
Run the benchmark command:
go test -bench . test/for_range_performance_test.go
Test results:
goos: darwin
goarch: amd64
BenchmarkForStruct-4 3176875 375 ns/op
BenchmarkRangeIndexStruct-4 3254553 369 ns/op
BenchmarkRangeStruct-4 3131196 384 ns/op
PASS
ok command-line-arguments 4.775s
It can be seen that:
for range The performance of traversing through Index and direct access to elements is almost no different from that of for traversing the traversal of index and for
Next, we add an array field val with byte type length 4096 in the Item structure
type Item struct { id int val [4096]byte }
Run the benchmark test again, and the results are as follows:
goos: darwin
goarch: amd64
BenchmarkForStruct-4 2901506 393 ns/op
BenchmarkRangeIndexStruct-4 3160203 381 ns/op
BenchmarkRangeStruct-4 1088 948678 ns/op
PASS
ok command-line-arguments 4.317s
It can be seen that:
- For range The performance of traversing elements through subscripts is not much different from that of for
- The performance of direct traversing elements for range is nearly 1,000 times slower than for
in conclusion:
- For range The performance of traversing elements through subscripts is not much different from that of for
- The performance of for range directly traversing elements is not much different from for when the element is a small object, and is much slower than for when the element is a large object.
The underlying principle of for range
For the implementation of for-range statements, the answer can be found in the compiler source code.
Compiler source codegofrontend/go//For_range_statement::do_lower() [see the link below
reference】
The following comments are included in the method.
// Arrange to do a loop appropriate for the type. We will produce // for INIT ; COND ; POST { // ITER_INIT // INDEX = INDEX_TEMP // VALUE = VALUE_TEMP // If there is a value // original statements // }
It can be seen that range is actually a C-style loop structure. range supports string, array, array pointer, slice, map and channel types, and there are some differences in details for different types.
1、range for slice
The following comment explains the process of traversing slices:
For_range_statement::lower_range_slice
// The loop we generate: // for_temp := range // len_temp := len(for_temp) // for index_temp = 0; index_temp < len_temp; index_temp++ { // value_temp = for_temp[index_temp] // index = index_temp // value = value_temp // original body // }
Before traversing the slice, the length of the slice will be obtained as the number of loops. In the loop body, the element value will be obtained first for each loop. If the index and value are received in for-range, the index and value will be assigned once, which explains that traversing large elements will affect performance, because the assignment of large objects will produce gc.
Since the number of loops has been determined before the loop starts, the newly added elements during the loop cannot be traversed.
In addition, the traversal process of array and array pointer is basically the same as slice, so I will not repeat it again.
2、range for map
The following comment explains the process of traversing maps:
For_range_statement::lower_range_map
// The loop we generate: // var hiter map_iteration_struct // for mapiterinit(type, range, &hiter); != nil; mapiternext(&hiter) { // index_temp = * // value_temp = * // index = index_temp // value = value_temp // original body // }
There is no specified number of loops when traversing the map, and the loop body is similar to traversing the slice. Since the underlying implementation of map is different from slice, map is implemented using a hash table, and the insertion data location is random, so the newly inserted data during the traversal cannot be guaranteed to be traversed.
3、range for channel
Traversing the channel is the most special, which is determined by the channel implementation mechanism:
For_range_statement::lower_range_channel
// The loop we generate: // for { // index_temp, ok_temp = <-range // if !ok_temp { // break // } // index = index_temp // original body // }
Read data in a loop, if there is data, it will be fetched, if there is no, it will be blocked, if the channel is closed, it will be exited
Note:
In the above comment, the index_temp is actually described incorrectly and should be value_temp, because index has no meaning to channel.
Summarize
Using index, the value to receive the range return value will generate a copy of the data, and the data will not be received depending on the situation to improve performance
The implementation of for-range is actually a C-style for loop
This is the article about how to use for range in go language and guide to avoid pits. For more relevant content on go language for range, please search for my previous articles or continue browsing the related articles below. I hope you will support me in the future!
References
【《Go Expert Programming》Go range implementation principle and performance optimization analysis /renhc/blog/2396058
[Interviewer: Have you used the for-range in Go? Can you explain the reasons for these questions? 】/p/217987219
【Go language high-performance programming】/post/
【gofrontend】/golang/gofrontend/blob/master/go/