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

推荐订阅源

小众软件
小众软件
博客园_首页
M
MIT News - Artificial intelligence
雷峰网
雷峰网
GbyAI
GbyAI
博客园 - 叶小钗
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
S
SegmentFault 最新的问题
H
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 【当耐特】
V
Visual Studio Blog
月光博客
月光博客
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
云风的 BLOG
云风的 BLOG
美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
有赞技术团队
有赞技术团队

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解
[golang编码小技巧]对数组元素赋值时,先赋值尾部再赋值头部...
ahfuzhang · 2026-05-06 · via 博客园_首页

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


最近看到一个非常棒的 protobuf 的库:github.com/planetscale/vtprotobuf
其性能非常强悍,我自己写的版本始终没干过它。(在我的新版推出以前)vtprotobuf 可以算是 golang 领域最快的 protobuf 库。

为什么我就比不过它呢?我看到了这样的看不懂的代码:

func (m *Child) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
	if m == nil {
		return 0, nil
	}
	i := len(dAtA)
	_ = i
	var l int
	_ = l
	if len(m.ChildName) > 0 {
		i -= len(m.ChildName)
		copy(dAtA[i:], m.ChildName)
		i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ChildName)))
		i--
		dAtA[i] = 0x12
	}
	if m.ChildId != 0 {
		i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ChildId))
		i--
		dAtA[i] = 0x8
	}
	return len(dAtA) - i, nil
}

可以发现,这个库的特点是: 先对数组的尾部赋值,然后下标向前偏移,然后再对数组首部进行赋值
难道这样就会变快?

Yes!
下面我就拆解一下变快的原因:

先看下面的两个函数:

func f1(arr []byte) {
    arr[0] = 1
    arr[9] = 2
}

func f2(arr []byte) {
    arr[9] = 2
    arr[0] = 1
}

功能完全一样,只是顺序不同。

下面用命令行来检查数组越界检查:

go tool compile -d=ssa/check_bce/debug=1 bce.go

可以发现:

func f1(arr []byte) {
    arr[0] = 1 // 仍有 bounds check
    arr[9] = 2 // 仍有 bounds check
}

func f2(arr []byte) {
    arr[9] = 2 // 仍有 bounds check
    arr[0] = 1 // 没有 Found,说明这个检查被消掉了
}

由此说明:如果先出现了比较大的下标,再出现小的下标,那么编译器就能推断后续的数组访问一定没越界,由此便不再产生越界检查的代码。

从 golang 源码本身也能发现证据:
Go 编译器源码证据主要在 cmd/compile/internal/ssa/prove.go。OpIsInBounds 表示一次下标越界检查;当它为真时,编译器会学习到 0 <= index < length。源码注释直接写了:对于 OpIsInBounds,正分支会学习 signed 域里的 0 <= a0 < a1,以及 unsigned 域里的 a0 < a1,然后调用 ft.update 记录 index 和 length 之间的关系。

希望对你有用。
Have func. 😃