2.3 for 和 for-range 的性能比较
...大约 3 分钟
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-range
和for
性能差不多,但是使用元素值遍历时,性能显著下降。
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
Powered by Waline v2.15.2