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

推荐订阅源

P
Privacy & Cybersecurity Law Blog
SecWiki News
SecWiki News
T
Troy Hunt's Blog
Y
Y Combinator Blog
V
V2EX
美团技术团队
Last Week in AI
Last Week in AI
S
Security @ Cisco Blogs
IT之家
IT之家
博客园_首页
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
AI
AI
罗磊的独立博客
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
N
News and Events Feed by Topic
P
Privacy International News Feed
V2EX - 技术
V2EX - 技术
Recent Commits to openclaw:main
Recent Commits to openclaw:main
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
GbyAI
GbyAI
L
LINUX DO - 热门话题
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Azure Blog
Microsoft Azure Blog
Martin Fowler
Martin Fowler
月光博客
月光博客
WordPress大学
WordPress大学
Latest news
Latest news
Google DeepMind News
Google DeepMind News
S
Schneier on Security
N
Netflix TechBlog - Medium
腾讯CDC
T
Tailwind CSS Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
L
LINUX DO - 最新话题
Project Zero
Project Zero
Cyberwarzone
Cyberwarzone
D
DataBreaches.Net
Webroot Blog
Webroot Blog
B
Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
H
Help Net Security
L
LangChain Blog
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
十年 | 重归混沌的BLOG
重归混沌 · 2025-08-31 · via 重归混沌的BLOG

随着我敲下 git push origin master,轻轻按下回车,这一轮漫长的重构终于告一段落。

翻看提交记录时才发现,这些被重构的代码居然大多写于十年前,心里不免一阵唏嘘。

从 Git 仓库的第一个提交时间来看,silly 这个坑是我在 2015 年 6 月 27 日开的。当时的初衷,不过是想练习一下服务器编程。

可随着一次次思考与重构,我渐渐改变了初心。

我希望它能真正跑在服务器项目里,而不仅仅是个玩具。

大约在 2018 年,我第一次在一个卡牌游戏的塔防玩法中,将 silly 引入了生产环境。

这一点真的要感谢领导的开明。虽然只是一个子玩法,但引入新框架总是存在风险,尤其是数据错乱这类问题。所幸,silly 在上线后并没有带来额外的 bug。

不过,在内网倒是踩过坑。由于我们使用私有协议,需要自定义 Marshal/Unmarshal。为了提升效率,我当时加了 Cache 来减少内存分配,结果在链表尾部忘了置 NULL。这个Bug一直提醒我至今,每次写链表都会特别小心。

随着稳定运行时间的增加,我对 silly 的信心也逐年增长。

在后来的 SLG 游戏 的服务器程序中,我干脆用 silly 重写了整个业务逻辑。只有“武将养成”部分依然保留着 C 代码,因为那些代码是从之前的 卡牌游戏 继承下来的。

可以说,在这10年里,我真的学会了很多,这个框架也一直在和我一起成长。

但也正因为它一直在线上稳定运行,面对一些核心模块(比如网络模块)的设计时,哪怕心里有了新的重构想法,也始终下不定决心去动。

直到最近,我决定再次研究 io_uring

之所以说“再次”,是因为早在 2021 年我就粗略看过它的原理。当时查到的结论是:在网络场景里,io_uring 并没有显著优势,于是便搁置了。

几年过去,以它的发展趋势,我觉得值得再看看如今的性能究竟如何。

于是我开了一个分支,用 io_uring 重写了网络部分(虽然实现还有点小 bug,但不影响测试数据)。结果显示:性能仅略快于当前版本。

升级计划虽然再次搁置,但在重构过程中我却发现,现有的设计在一些地方已经显得颇为些掣肘。

再加上 2022 年工作上的变动,silly 继续用于生产环境的可能性也不大了。

我想,是时候为 silly 重新定位了。 它将作为我编程思考与最新领悟的承载体。

当然,我依然会尽可能编写完善的测试来保证质量,只是在设计的选择上可能会更激进一些。

就在前几个月,我还跟别人感慨过:“我觉得自己写代码终于算是入门了。”但要我清晰解释“入门”的含义,却又说不清。

刚好可以借着这次对 silly 的重构,来使用代码解读一下我的感受。

在写这篇文章时,我依然无法完整表达“入门”的含义,但似乎已经能尝试用一些场景来说明。


我刚毕业时,公司使用的 XML 文件巨大(上百 MB)。

即便是 tinyXML,在性能上也有些吃力。于是我从周六中午开始,直到晚上 11 点左右,写了一个简易的 XML 解析器。

具体性能我已记不清了,但至少比常规 XML 解析器快了 3~5 倍。至少在程序里选择芯片时,原本卡顿的界面不再那么难以忍受。

当然,世上没有银弹,我也没强到逆天。

真正的原因是:我根据公司 XML 文件的特点,结合内存分配和 CPU Cache 等机制,对代码做了高度定制化。

换句话说,换一类复杂点的 XML 文件,我的解析器可能就不行了,也未必快。

有个小插曲:我把设计思路讲给同事听,他也实现了一个,但性能总比我略慢。可惜当时只顾着沾沾自喜,没有仔细对比差异在哪里。

如果是十年后的我再遇到这个问题,我可能不会做出相同的选择。

我可能会先考虑:到底是什么导致了卡顿?是 CPU 算力不足,还是单核性能不够,还是文件过大、IO 过慢?

假如确认是单核性能不足,我可能会考虑在打包安装包时,提前把 XML 拆成多个文件,运行时多线程加载,再合并数据。

如果多线程依然慢,我会结合表现层需求(界面是下拉框 + 芯片 ListBox,当切换厂商时显示对应芯片)去做二级索引:

  • 每个厂商的芯片数据预处理成独立 XML;
  • 再生成一张厂商表;
  • 打开界面时,只加载厂商列表和默认厂商的芯片数据;
  • 切换厂商时,按需加载对应文件;
  • 甚至后台异步加载所有文件。

这样,流畅度提升的数量级,绝对比单纯重写解析器要大得多。

在我之后的职业生涯中,这种“通过裁剪功能或换思路来提升性能”的场景一次次重演。


再说回这次的重构。跨线程通信时,我需要用到队列。

巧的是,当年写 XML 解析器的那个程序,也用了多线程。一个上位机要同时和 64 块烧录板通信。

为了简单,我给每个烧录板开了一个线程。正常烧录一切顺利,直到电饭煲厂家提了个新需求:每个设备都要烧录一个唯一的 device id,而且 id 由他们提供。于是我写了个 SPMC 队列来解决 64 个线程的竞争问题。

现在回头看,当时是否真的有必要,仍是个问号。我甚至没去验证这个队列相比加锁快多少,或者说“快这么多”到底有没有意义。

这次我特意问了 AI,它强烈推荐我用 SPSC 队列,因为通信只有两个线程。

但仔细思考后,我还是拒绝了。最终实现了一个简单的 PING-PONG 双缓冲机制来替代重锁。

相比 SPSC 队列的复杂性,在性能牺牲不大的情况下,我更倾向于选择更直观的设计。


有时候,我们看到别人的代码非常简单,我们可能会说,这么简单的代码谁不会写。

可当自己面对相同问题时,能否也选择如此简单的方案,这才是对我们的考验。

正如乔峰聚贤庄一战,太祖长拳照样胜过群雄——这便是“化腐朽为神奇”的能力。

我想说的“入门”大抵就是如此了, 能用最常规的数据结构和实现来解决问题,而不是像过去那样,总想着在局部动刀裁剪来换性能,而忽视了从全局出发去思考。

当然,还有一些难以言说的感受。

PS:既然我说“入门”,就意味着我还远未精通,只是刚好触到这一层认知而已。