If we want to iterate over an array, Map collection, Slice Slice, etc., Go (Golang) provides a useful For Range method. Range is a keyword for ranges. Used with for to iterate over collections of arrays, maps, and so on. Its usage is simple, and map, channel and so on all use for range mode, so we use for range in the coding cycle iteration is the most. For this most commonly used iteration, especially for I =0; i

Basic usage

For range is very simple to use. Here are two collection types.

package main

import "fmt"

func main(a) {
	ages:=[]string{"10"."20"."30"}

	for i,age:=range ages{
		fmt.Println(i,age)
	}
}
Copy the code

The range keyword returns two variables I,age. The first is the index of the Slice and the second is the contents of the Slice, so we print it out:

0, 10, 1, 20, 2, 30Copy the code

Slice Slice on the language of the Go, you can refer to this article I wrote before the language field notes (5) | Go Slice

Let’s look at an example of map (dictionary) using for range.

package main

import "fmt"

func main(a) {
	ages:=map[string]int{"Zhang":15."Bill":20."Wang Wu":36}

	for name,age:=range ages{
		fmt.Println(name,age)
	}
}
Copy the code

When iterating through a map using for range, the first variable returned is key and the second variable is value, which is the name and ages in our example. Let’s run the program and see the output.

Zhang SAN 15 Li Si 20 Wang Wu 36Copy the code

Note that the order of k-V key-value pairs returned by the for range map is not fixed, and it is random. This time, It is possible that Zhang San-15 appears first, and the next run may be Wang Wu-36 is printed first. On the Map more detailed you can refer to my previous article Go language field notes (6) | Go Map.

General for loop comparison

For example, for Slice slices, we have two iterations: one is the conventional for I :=0; i

func ForSlice(s []string) {
	len: =len(s)
	for i := 0; i < len; i++ {
		_, _ = i, s[i]
	}
}

func RangeForSlice(s []string) {
	for i, v := range s {
		_, _ = i, v
	}
}
Copy the code

To test, we wrote these two functions that iterate over Slice slices. From the implementation point of view, their logic is the same, ensuring that we can test in the same situation.

import "testing"

const N  =  1000

func initSlice(a) []string{
	s:=make([]string,N)
	for i:=0; i<N; i++{ s[i]="www.flysnow.org"
	}
	return s;
}

func BenchmarkForSlice(b *testing.B) {
	s:=initSlice()

	b.ResetTimer()
	for i:=0; i<b.N; i++ { ForSlice(s) } }func BenchmarkRangeForSlice(b *testing.B) {
	s:=initSlice()

	b.ResetTimer()
	for i:=0; i<b.N; i++ { RangeForSlice(s) } }Copy the code

The Bench benchmark use case simulates traversal of 1000 Slice slices under the same conditions. Then we run go test-bench =. -run=NONE to see the performance test results.

BenchmarkForSlice-4              5000000    287 ns/op
BenchmarkRangeForSlice-4         3000000    509 ns/op
Copy the code

As you can see from the performance test, the performance of a regular for loop is nearly twice as high as that of a for range, and I believe you already know the reason. Yes, because the for range is a copy of the elements of the loop each time, so the more complex the budget within the set, the worse the performance. On the contrary, the regular for loop, It retrieves the elements of the collection by means of s[I], an index pointer reference, which is much better than copying.

Since element copying is a problem, we iterate over Slice slices to get elements, so we implement for range in a different way.

func RangeForSlice(s []string) {
	for i, _ := range s {
		_, _ = i, s[i]
	}
}
Copy the code

Now, let’s run the Benchmark performance test again to see what happens.

BenchmarkForSlice-4              5000000    280 ns/op
BenchmarkRangeForSlice-4         5000000    277 ns/op

Copy the code

Well, as we expected, performance is up, and it’s on par with regular for loops. The reason is that we get rid of the copy of the element by _, and then get the iterated element by S [I], both improving performance and achieving the goal.

The Map traverse

We can’t use for I :=0 for Map; i

func RangeForMap1(m map[int]string) {
	for k, v := range m {
		_, _ = k, v
	}
}

const N = 1000

func initMap(a) map[int]string {
	m := make(map[int]string, N)
	for i := 0; i < N; i++ {
		m[i] = fmt.Sprint("www.flysnow.org",i)
	}
	return m
}

func BenchmarkRangeForMap1(b *testing.B) {
	m:=initMap()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		RangeForMap1(m)
	}
}
Copy the code

www.flysnow.org/

Snow ruthless blog

This article is an original article, reprinted with notes of origin, “there are always bad people grab the article when also remove my original description” welcome to scan the code to follow the public number flysnow_org or www.flysnow.org/, the first time to see the follow-up wonderful article. “Anti-rotten person remarks **…… &*¥” feel good, feel free to share it in moments, thank you for your support.

The above example is the map traversal function and the benchmark test, I have written together, run the test to see the effect.

BenchmarkForSlice-8              5000000    298 ns/op
BenchmarkRangeForSlice-8         3000000    475 ns/op
BenchmarkRangeForMap1-8           100000    14531 ns/op
Copy the code

Map traversal performance is even worse than Slice’s, which is pretty pathetic. Ok, let’s start optimization, the idea is to reduce the worth of copying. The RangeForSlice in the test was also slow because I restored the RangeForSlice to something worth copying to compare performance.

func RangeForMap2(m map[int]string) {
	for k, _ := range m {
		_, _ = k, m[k]
	}
}

func BenchmarkRangeForMap2(b *testing.B) {
	m := initMap()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		RangeForMap2(m)
	}
}
Copy the code

Run the performance test again to see what happens.

BenchmarkForSlice-8              5000000               298 ns/op
BenchmarkRangeForSlice-8         3000000               475 ns/op
BenchmarkRangeForMap1-8           100000             14531 ns/op
BenchmarkRangeForMap2-8           100000             23199 ns/op

Copy the code

Well, if you noticed the dot error, the performance of the method BenchmarkRangeForMap2 was significantly degraded, as can be seen from the elapsed time per operation (although the number of performance test seconds was the same). Unlike the Slice we tested above, not only did it not improve, it actually dropped.

Continue to modify the implementation of Map2 function as:

func RangeForMap2(m map[int]Person) {
	for  range m {
	}
}
Copy the code

Do nothing but iterate and run performance tests again.

BenchmarkForSlice-8              5000000               301 ns/op
BenchmarkRangeForSlice-8         3000000               478 ns/op
BenchmarkRangeForMap1-8           100000             14822 ns/op
BenchmarkRangeForMap2-8           100000             14215 ns/op

Copy the code

* We were surprised to find that the performance of doing nothing is the same as that of obtaining k-V values, and completely different from that of Slice. Where did it all go? And let’s take a guess. We can do that in the next video

For the range principle

Check out github.com/golang/gofr… Source code, we can find the implementation of for range is:

// 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
  / /}
Copy the code

For Slice,Map, etc., there are specific different compiler implementation. Let’s first look at the specific implementation of for 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
  / /}
Copy the code

A copy is made of the Slice to be traversed, the length is retrieved, and a regular for loop is used to traverse and a copy of the value is returned.

Let’s look at the implementation of the for range map:

  // The loop we generate:
  // var hiter map_iteration_struct
  // for mapiterinit(type, range, &hiter); hiter.key ! = nil; mapiternext(&hiter) {
  // index_temp = *hiter.key
  // value_temp = *hiter.val
  // index = index_temp
  // value = value_temp
  // original body
  / /}
Copy the code

The map is initialized first, and since the map is a * hashMap, this is actually a copy of the * hashMap pointer.

Combining these two specific for range compiler implementations, we can see why the _ optimization for range slice works and the for range map doesn’t. Welcome to leave a comment.

This article is an original article, reprinted with notes of origin, “there are always bad people grab the article when also remove my original description” welcome to scan the code to follow the public number flysnow_org or www.flysnow.org/, the first time to see the follow-up wonderful article. “Anti-rotten person remarks **…… &*¥” feel good, feel free to share it in moments, thank you for your support.