2.3 for 和 for-range 的性能比较

Kesa...大约 3 分钟golang

1. 遍历简单元素的切片

以整型切片为例:

func iterUsingFor(a []int) {
	var res int
	for i := 0; i < len(a); i++ {
		res = a[i]
	}
	_ = res
}

func iterUsingRangeIdx(a []int) {
	var res int
	for i := range a {
		res = a[i]
	}
	_ = res
}

func iterUsingRangeVal(a []int) {
	var res int
	for _, v := range a {
		res = v
	}
	_ = res
}

// test funcs 

func BenchmarkIterIntSlice(b *testing.B) {
	tests := []struct {
		name string
		f    func([]int)
	}{
		{name: "UsingFor", f: iterUsingFor},
		{name: "UsingRangeIdx", f: iterUsingRangeIdx},
		{name: "UsingRangeVal", f: iterUsingRangeVal},
	}

	for k := 0; k <= 100000; k *= 10 {
		nums := genSliceWithCap(k)

		for _, tt := range tests {
			b.Run(fmt.Sprintf("%-20s_%.e", tt.name, float64(k)), func(b *testing.B) {
				for i := 0; i < b.N; i++ {
					tt.f(nums)
				}
			})
		}

		if k == 0 {
			k = 1
		}
	}

}

BenchmarkIterIntSlice/UsingFor_____________0e+00-12             648061189                1.799 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________0e+00-12             558745578                2.117 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________0e+00-12             659441024                1.817 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingFor_____________1e+01-12             235078812                4.992 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________1e+01-12             243918748                4.898 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________1e+01-12             238101757                4.989 ns/op           0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingFor_____________1e+02-12             31692288                34.52 ns/op            0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________1e+02-12             35111434                32.87 ns/op            0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________1e+02-12             34526412                34.20 ns/op            0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingFor_____________1e+03-12              4440621               280.1 ns/op             0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________1e+03-12              4533476               262.6 ns/op             0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________1e+03-12              4577653               260.0 ns/op             0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingFor_____________1e+04-12               440082              2529 ns/op               0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________1e+04-12               448012              2545 ns/op               0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________1e+04-12               441699              2667 ns/op               0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingFor_____________1e+05-12                47096             25617 ns/op               0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeIdx________1e+05-12                47168             25343 ns/op               0 B/op          0 allocs/op
BenchmarkIterIntSlice/UsingRangeVal________1e+05-12                47026             25219 ns/op               0 B/op          0 allocs/op

可以看出三种遍历方式差距不大。

2. 遍历 结构体 切片


type MySt struct {
	id   int
	data [1024]int
}

func iterMsUsingFor(a []MySt) {
	var res MySt
	for i := 0; i < len(a); i++ {
		res = a[i]
	}
	_ = res
}

func iterMsUsingRangeIdx(a []MySt) {
	var res MySt
	for i := range a {
		res = a[i]
	}
	_ = res
}

func iterMsUsingRangeVal(a []MySt) {
	var res MySt
	for _, v := range a {
		res = v
	}
	_ = res
}

// tests

func BenchmarkIterStructSlice(b *testing.B) {
	tests := []struct {
		name string
		f    func([]MySt)
	}{
		{name: "UsingFor", f: iterMsUsingFor},
		{name: "UsingRangeIdx", f: iterMsUsingRangeIdx},
		{name: "UsingRangeVal", f: iterMsUsingRangeVal},
	}

	for k := 0; k <= 1000; k *= 10 {
		a := make([]MySt, k)
		for _, tt := range tests {
			b.Run(fmt.Sprintf("%-20s_%.e", tt.name, float64(k)), func(b *testing.B) {
				for i := 0; i < b.N; i++ {
					tt.f(a)
				}
			})
		}

		if k == 0 {
			k = 1
		}
	}

}
BenchmarkIterStructSlice/UsingFor_____________0e+00-12          665996229                1.843 ns/op
BenchmarkIterStructSlice/UsingRangeIdx________0e+00-12          569261311                2.073 ns/op
BenchmarkIterStructSlice/UsingRangeVal________0e+00-12          599262607                2.030 ns/op
BenchmarkIterStructSlice/UsingFor_____________1e+01-12          255458889                4.777 ns/op
BenchmarkIterStructSlice/UsingRangeIdx________1e+01-12          232607139                4.888 ns/op
BenchmarkIterStructSlice/UsingRangeVal________1e+01-12            759051              1472 ns/op
BenchmarkIterStructSlice/UsingFor_____________1e+02-12          35052460                34.58 ns/op
BenchmarkIterStructSlice/UsingRangeIdx________1e+02-12          39883009                32.57 ns/op
BenchmarkIterStructSlice/UsingRangeVal________1e+02-12             71300             15636 ns/op
BenchmarkIterStructSlice/UsingFor_____________1e+03-12           4288478               268.2 ns/op
BenchmarkIterStructSlice/UsingRangeIdx________1e+03-12           4752506               267.9 ns/op
BenchmarkIterStructSlice/UsingRangeVal________1e+03-12              4719            262072 ns/op

可以看出使用索引的for-rangefor性能差不多,但是使用元素值遍历时,性能显著下降。

3. 遍历 结构体指针 切片

若将结构体换成指针:


func iterMsPtrUsingFor(a []*MySt) {
	var res *MySt
	for i := 0; i < len(a); i++ {
		res = a[i]
	}
	_ = res
}

func iterMsPtrUsingRangeIdx(a []*MySt) {
	var res *MySt
	for i := range a {
		res = a[i]
	}
	_ = res
}

func iterMsPtrUsingRangeVal(a []*MySt) {
	var res *MySt
	for _, v := range a {
		res = v
	}
	_ = res
}


func genMsSlice(n int) []*MySt {
	res := make([]*MySt, 0, n)
	for i := 0; i < n; i++ {
		res = append(res, &MySt{})
	}
	return res
}

// tests
func BenchmarkIterStructPtrSlice(b *testing.B) {
	tests := []struct {
		name string
		f    func([]*MySt)
	}{
		{name: "UsingFor", f: iterMsPtrUsingFor},
		{name: "UsingRangeIdx", f: iterMsPtrUsingRangeIdx},
		{name: "UsingRangeVal", f: iterMsPtrUsingRangeVal},
	}

	for k := 0; k <= 1000; k *= 10 {
		a := genMsSlice(k)
		for _, tt := range tests {
			b.Run(fmt.Sprintf("%-20s_%.e", tt.name, float64(k)), func(b *testing.B) {
				for i := 0; i < b.N; i++ {
					tt.f(a)
				}
			})
		}

		if k == 0 {
			k = 1
		}
	}

}

再次测试:

BenchmarkIterStructPtrSlice/UsingFor_____________0e+00-12               558088916                2.064 ns/op
BenchmarkIterStructPtrSlice/UsingRangeIdx________0e+00-12               679685667                1.757 ns/op
BenchmarkIterStructPtrSlice/UsingRangeVal________0e+00-12               576765020                2.185 ns/op
BenchmarkIterStructPtrSlice/UsingFor_____________1e+01-12               244455494                4.911 ns/op
BenchmarkIterStructPtrSlice/UsingRangeIdx________1e+01-12               253984057                4.843 ns/op
BenchmarkIterStructPtrSlice/UsingRangeVal________1e+01-12               240997875                4.937 ns/op
BenchmarkIterStructPtrSlice/UsingFor_____________1e+02-12               31083170                33.86 ns/op
BenchmarkIterStructPtrSlice/UsingRangeIdx________1e+02-12               34513502                34.15 ns/op
BenchmarkIterStructPtrSlice/UsingRangeVal________1e+02-12               37010649                32.97 ns/op
BenchmarkIterStructPtrSlice/UsingFor_____________1e+03-12                4621555               271.9 ns/op
BenchmarkIterStructPtrSlice/UsingRangeIdx________1e+03-12                4567570               268.8 ns/op
BenchmarkIterStructPtrSlice/UsingRangeVal________1e+03-12                4618844               267.9 ns/op

此时三者性能差距不大。

4. 原因

for-range遍历元素时,每次都会进行一次拷贝,当元素内存占用比较高时,会降低内存和时间性能。

所以,在遍历时应注意目标元素的类型及其内存占用情况选择合适的方式遍历。

Reference

  1. https://geektutu.com/post/hpg-range.htmlopen in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2