郑文峰的博客 郑文峰的博客
首页
  • Go语言高性能编程
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)

zhengwenfeng

穷则变,变则通,通则久
首页
  • Go语言高性能编程
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)
  • python

  • go语言

    • go简单使用grpc
    • gin中validator模块的源码分析
    • 优化gin表单的错误提示信息
    • go中如何处理error
    • tcp缓存引起的日志丢失
    • 使用etcd分布式锁导致的协程泄露与死锁问题
    • go语言高性能编程

      • Go协程池深度解析:原理、实现与最佳实践
      • Go语言Interface Boxing原理与性能优化指南
      • Go语言遍历性能深度解析:从原理到优化实践
        • 前言
        • 三种遍历方式对比
        • []int Benchmark测试
        • []struct Benchmark 测试
        • []*struch Benchmark 测试
        • 总结
      • Go语言零拷贝技术完全指南
      • Go语言不可变数据共享:无锁并发编程实践
      • Go语言内存预分配完全指南
      • Go语言原子操作完全指南
      • Go语言堆栈分配与逃逸分析深度解析
      • Go语言空结构体:零内存消耗的高效编程
      • Go语言结构体内存对齐完全指南
      • Go语言字符串拼接性能对比与优化指南
      • Go语言延迟初始化(Lazy Initialization)最佳实践
      • Go语言高效IO缓冲技术详解
  • linux

  • 其他

  • 编程
  • go语言
  • go语言高性能编程
zhengwenfeng
2025-06-14
目录

Go语言遍历性能深度解析:从原理到优化实践

# 前言

在Go语言中,遍历是日常开发中最常见的操作之一。不同的遍历方式会对性能产生显著影响。本文将深入分析:

  1. 基本切片遍历的性能特点
  2. 结构体切片的遍历优化
  3. 指针切片的性能优势
  4. 实际场景中的最佳实践

# 三种遍历方式对比

Go语言中主要有三种遍历切片的方式:

// 1. 索引遍历
for i := 0; i < len(slice); i++ {
    // 使用slice[i]
}

// 2. range遍历值
for _, v := range slice {
    // 使用v
}

// 3. range遍历索引和值
for i, v := range slice {
    // 使用i和v
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# []int Benchmark测试

func BenchmarkIndexLoop(b *testing.B) {
    slice := make([]int, 1000)
    for i := 0; i < b.N; i++ {
        for j := 0; j < len(slice); j++ {
            _ = slice[j]
        }
    }
}

func BenchmarkRangeValue(b *testing.B) {
    slice := make([]int, 1000)
    for i := 0; i < b.N; i++ {
        for _, v := range slice {
            _ = v
        }
    }
}

func BenchmarkRangeIndexValue(b *testing.B) {
    slice := make([]int, 1000)
    for i := 0; i < b.N; i++ {
        for j, v := range slice {
            _, _ = j, v
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

测试结果如下,可以发现三者性能接近,基本相差不大。

% go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkIndexLoop-12            4721586               235.7 ns/op             0 B/op          0 allocs/op
BenchmarkRangeValue-12           5085130               234.3 ns/op             0 B/op          0 allocs/op
BenchmarkRangeIndexValue-12      5101604               233.9 ns/op             0 B/op          0 allocs/op
PASS
ok      main/demo       4.560s
1
2
3
4
5
6
7
8
9
10

# []struct Benchmark 测试


type Item struct {
	ID   int
	Data [4096]byte // 增加数据量以放大性能差异
}

func BenchmarkStructIndex(b *testing.B) {
	var slice [1000]Item
	for i := 0; i < b.N; i++ {
		var tmp int
		for j := 0; j < len(slice); j++ {
			tmp = slice[j].ID
		}
		_ = tmp
	}
}

func BenchmarkStructRangeValue(b *testing.B) {
	var slice [1000]Item
	for i := 0; i < b.N; i++ {
		var tmp int
		for _, v := range slice {
			tmp = v.ID
		}
		_ = tmp
	}
}

func BenchmarkStructRangeIndexValue(b *testing.B) {
	var slice [1000]Item
	for i := 0; i < b.N; i++ {
		var tmp int
		for j := range slice {
			tmp = slice[j].ID
		}
		_ = tmp
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

运行结果如下,可以发现通过索引的方式取值的两种方式相差不大,但是直接取值的方式性能差了 500 多倍,这是因为直接取值时会进行数据的复制,而索引取值不会进行数据的复制。

$ go test  -bench=. -benchmem . 
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStructIndex-12                  4735326               237.9 ns/op             0 B/op          0 allocs/op
BenchmarkStructRangeValue-12               17096             69135 ns/op               0 B/op          0 allocs/op
BenchmarkStructRangeIndexValue-12        5123646               234.6 ns/op             0 B/op          0 allocs/op
PASS
ok      main/demo       4.983s
1
2
3
4
5
6
7
8
9
10

# []*struch Benchmark 测试


type Item struct {
	ID   int
	Data [4096]byte // 增加数据量以放大性能差异
}

func BenchmarkStructIndex(b *testing.B) {
	var slice [1000]*Item
	for i := range slice {
		slice[i] = &Item{}
	}
	for i := 0; i < b.N; i++ {
		var tmp int
		for j := 0; j < len(slice); j++ {
			tmp = slice[j].ID
		}
		_ = tmp
	}
}

func BenchmarkStructRangeValue(b *testing.B) {
	var slice [1000]*Item
	for i := range slice {
		slice[i] = &Item{}
	}
	for i := 0; i < b.N; i++ {
		var tmp int
		for _, v := range slice {
			tmp = v.ID
		}
		_ = tmp
	}
}

func BenchmarkStructRangeIndexValue(b *testing.B) {
	var slice [1000]*Item
	for i := range slice {
		slice[i] = &Item{}
	}
	for i := 0; i < b.N; i++ {
		var tmp int
		for j := range slice {
			tmp = slice[j].ID
		}
		_ = tmp
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

运行结果如下,三者性能相近,但是有个好处是可以直接修改指针对应结构体的值。

go test  -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStructIndex-12                  1864108               656.9 ns/op             2 B/op          0 allocs/op
BenchmarkStructRangeValue-12             1577792               748.6 ns/op             3 B/op          0 allocs/op
BenchmarkStructRangeIndexValue-12        1843874               661.4 ns/op             2 B/op          0 allocs/op
PASS
ok      main/demo       5.995s
1
2
3
4
5
6
7
8
9
10

# 总结

根据测试结果,我们得出以下结论:

  1. 基础类型切片:三种遍历方式性能相当,可按编码习惯选择
  2. 大结构体切片:
    • 避免使用for _, v := range直接值遍历
    • 优先使用索引遍历或range索引遍历
  3. 需要修改元素时:
    • 使用指针切片([]*T)性能接近且更灵活
  4. 性能关键路径:
    • 对大结构体集合操作,索引遍历性能最优
    • 对小结构体或基础类型,差异可忽略

记住:没有绝对的最佳方式,只有最适合当前场景的选择!

#go语言#go语言高性能编程
上次更新: 2025/06/14, 16:16:07
Go语言Interface Boxing原理与性能优化指南
Go语言零拷贝技术完全指南

← Go语言Interface Boxing原理与性能优化指南 Go语言零拷贝技术完全指南→

最近更新
01
Go语言高效IO缓冲技术详解
06-14
02
Go语言延迟初始化(Lazy Initialization)最佳实践
06-14
03
Go语言字符串拼接性能对比与优化指南
06-14
更多文章>
Theme by Vdoing | Copyright © 2022-2025 zhengwenfeng | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式