惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
人人都是产品经理
人人都是产品经理
IT之家
IT之家
T
The Blog of Author Tim Ferriss
V
V2EX
博客园 - 聂微东
The Cloudflare Blog
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
U
Unit 42
Vercel News
Vercel News
L
LangChain Blog
博客园 - 司徒正美
H
Help Net Security
Recent Announcements
Recent Announcements
Recorded Future
Recorded Future
V
Visual Studio Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
Y
Y Combinator Blog
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
B
Blog RSS Feed
F
Fortinet All Blogs
B
Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
MongoDB | Blog
MongoDB | Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
罗磊的独立博客
博客园_首页
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
量子位
I
InfoQ
小众软件
小众软件
P
Proofpoint News Feed

Random Thoughts

LLM 训练与推理的基本理解 昨日的世界不再重来 Magic Brush - 画出你自己的产品宇宙 写在 AI Coding 奇点之后 尼泊尔布恩山小环线纪行 2025 投资组合年报 三十而笠 我的 8 年职业生涯回顾 我的颈椎病康复之旅 - 关于选择的故事 熊野古道中边路纪行 新加坡旅居三年再回首 高加索三国 - 阿塞拜疆行记 2024 投资组合年报 重新思考 Go:了解程序在线上是如何运行的 欧游散记 —— 环勃朗峰之旅 欧游散记 —— 特摩索斯古城 重新思考 Go:Channel 不是「消息队列」 重新思考 Go:Slice 只是「操作视图」 那一天,我决定踏出一步 True Story 少数价值 A Snapshot of Myself - 2021 RPC 漫谈: 连接问题 RPC 漫谈:序列化问题 RPC 漫谈: 限流问题 科学,技术与工程 Pond: Golang 通用对象池 Golang Interface 内部实现 真理的有限性 Golang rand 库锁竞争优化 Meaningless or Meaningful 分布式文件系统的演化 从动物森友会聊主机游戏联机机制 创业公司的文化 伊朗见闻录 NodeJS 内存泄漏检测与定位 设计实现高性能本地内存缓存 Travel Map Linux I/O 栈浅析 SSD 背后的奥秘 什么是真正的编程能力 沉默与反沉默的理由 命令行里的设计艺术 自由意志下的选择 学习思维方式而非学习观点 从程序到人 —— 情头配对助手的前世今生 即刻多端实时通信实践 朝鲜 —— 小国寡民的主体思想实践 使用 Surge 提升多网络环境下的流畅开发体验 一份其实好吃的 LaTeX 入门餐 Kafka 的设计与实践思考 欧游散记 —— 民主专制下的德国 欧游散记 —— 维也纳 欧游散记 —— 西班牙 欧游散记 —— 德国国会大厦 社会矛盾讨论统一框架的一种可能性 欧游散记 —— 伪君子布拉格 Lemon : Koa 风格的 Python 异步 Web 框架 Golang : Make Programming Happy Again Podcast 闲言碎语 Kubernetes 中使用 API Gateway 替代 Ingress ElasticSearch 最佳实践 blog.joway.io Presentations
Golang for-range 内部实现
Joway · 2021-01-20 · via Random Thoughts

最近在写一个编解码的功能时发现使用 Golang for-range 会存在很大的性能问题。

假设我们现在有一个 Data 类型表示一个数据包,我们从网络中获取到了 [1024]Data 个数据包,此时我们需要对其进行遍历操作。一般我们会使用 for-i++ 或者 for-range 两种方式遍历,如下代码:

type Data [256]byte

func BenchmarkForStruct(b *testing.B) {
	var items [1024]Data
	var result Data
	for i := 0; i < b.N; i++ {
		for k := 0; k < len(items); k++ {
			result = items[k]
		}
	}
	_ = result
}

func BenchmarkRangeStruct(b *testing.B) {
	var items [1024]Data
	var result Data
	for i := 0; i < b.N; i++ {
		for _, item := range items {
			result = item
		}
	}
	_ = result
}

输出结果:

BenchmarkForStruct-8     	 1697805	       652 ns/op
BenchmarkRangeStruct-8   	   60556	     19837 ns/op

可以看到通过索引来遍历的方式要比直接使用 for-range 快了近 30 倍。

索引遍历就是单纯地去访问数组的每个元素。而对于 for-range 循环,Golang 会根据迭代对象类型,已经 range 前的参数,对其进行不同形式的展开。对于以下 range 代码:

for i, elem := range a {}

编译器会将其转换成如下形式(伪代码), range.go

ha := a // 值拷贝
hn := len(ha) // 提前保存长度
hv1 := 0 // 当前遍历索引值
v1 := hv1 // 保存当前索引
v2 := nil // 保存当前值
for ; hv1 < hn; hv1++ {
    v1, v2 = hv1, ha[hv1] // 值拷贝
    ...
}

这里有几点需要额外注意:

  1. 编译器提前保存了元素长度,所以运行过程中即便长度变化,也不会影响循环次数
  2. ha := a 这一步会进行一次值拷贝,这里部分情况下可能会存在性能问题 (如上面的 [256]byte 类型,每次拷贝都有很大内存开销)
  3. v1, v2 = hv1, ha[hv1] 会对数组元素进行一次值拷贝
  4. v1, v2 预先创建,地址不会改变,对应到原始代码就是 for i, elem := range a {} 中的 i, elem 在每次循环时,都是同一个变量。

由此可以发现,当被迭代对象的元素为拷贝开销较大的类型时,使用 for-range 循环会存在很大的性能问题。此时更加建议使用标准 for 循环。