首页/快讯/我向以太坊客户端Geth提交了一行代码优化—这件事改变了我对开源的认知

我向以太坊客户端Geth提交了一行代码优化—这件事改变了我对开源的认知

事情开始得有点偶然。

我向以太坊客户端Geth提交了一行代码优化—这件事改变了我对开源的认知

那段时间我在研究以太坊的底层实现,读Geth的源码,Geth是Go语言写的以太坊客户端,整个网络里大部分节点跑的都是它,代码量很大,结构复杂,但读进去之后发现,Go的语法干净,逻辑清晰,读起来并不痛苦,大概连续读了两周之后,我注意到一段函数,处理的是交易池里的过期交易清理逻辑。

那段函数里有一个循环,循环内部每次都要重新计算一个固定的哈希值,这个哈希值在循环中不会被改变,但它重复计算了很多次,我当时想,如果把它提到循环外面,生成之后直接复用,那每次清理交易池的时候就能省下几微秒到几毫秒的时间,这个优化看起来很小,但在高负载的节点上,交易池清理是一个高频操作,频繁触发,几微秒累积起来就是可观的性能提升。

我决定试一试。

第一次提交代码的过程比我想象中麻烦得多,Geth项目用的是GitHub上的Pull Request流程,但它对代码规范和提交说明有极其严格的要求,你可以想象,一个已经有超过十年历史、被全球数千个节点依赖的核心项目,代码审核的严苛程度远超普通的公司项目。

我先是fork了仓库,拉取最新代码,然后在本地写了一个分支,改动的代码量很小,大概就三行:把那个哈希值计算的语句从循环体内移到循环体外,再调整一下变量作用域,然后我跑测试,确保所有的单元测试通过,再执行go lint检查代码风格,一切都通过之后,我写了提交信息。

我需要用英文写,而且要写清楚“为什么这样改”,我写了大概四五行,说明这个哈希值在循环内是常量,提前计算可以减少内存分配和计算开销,然后贴上了一些简单的基准测试数据。

我提交了PR。

接下来就是等待。

社区的反应比我想象中快,大概过了六个小时,就有维护者回复了,不是人类,是一个机器人,它自动运行了所有的CI流程,测试了多个平台,然后在评论区贴出了测试结果:测试通过,但真正让我紧张的是,一位核心维护者随后留了言,他没有直接说同意或者拒绝,而是问了一个问题:“这段函数在其他地方也被调用吗?那个调用的上下文中,循环次数会更大吗?”

这是一个好问题,我立刻检查了整个项目的引用,我发现这个函数不仅被交易池清理调用,还被一些调试相关的API间接调用,在那些API的调用场景里,循环次数可能达到数万次,这意味着我的优化在那些情况下收益更大,我把这个发现回复了过去,还补充了一份更完整的基准测试数据,覆盖了不同循环次数下的性能对比。

对方没有立刻回,又过了大概两天,另一位维护者在下面回复了,他说:“代码改动没问题,但请把变量命名改得更清晰一些,当前的命名容易让读者误解它的生命周期。”我看了看,他说得对,原来的变量名确实有点模糊,我改了之后,重新提交了一次,又过了一天,有人approve了,然后被合入了主分支。

那一刻其实没有太多激动,更多的是一种踏实的感觉,就好像你写了一首诗,然后有人把它刻在了石头上。

这次经历让我开始重新理解开源贡献这件事。

我以前觉得开源贡献就是“提交代码”,改一个bug,加一个功能,然后别人用到了,就算成功,但实际上,真正的开源贡献远不止代码,它涉及沟通、文档、测试、代码审查,甚至是你对项目生态的理解,Geth的维护者们在回复我的时候,从来没有表现出任何不耐烦,哪怕我提交的只是一行代码的改动,他们认真对待每一段代码,每一个问题,他们不但要看代码改得对不对,还要看代码是否易于维护、是否影响其他模块、未来的读者能否理解。

更深一层,我发现开源贡献的本质是一种信任的建立,你提交代码,是向项目提交你的判断,项目接收它,是认可你的判断,这个信任链条非常脆弱,一个错误的提交可能会引入安全问题,一个性能优化可能因为考虑不周而引发其他bug,维护者之所以严格,不是因为他们脾气差,而是因为他们对成千上万依赖这个软件的节点负责。

我后来又提交了几次优化,一次是改进了数据同步模块里一个不太明显的锁竞争问题,那个场景下,两个goroutine会同时尝试访问同一把锁,但实际业务逻辑中完全不需要同步,我通过调整goroutine启动顺序,让它们在关键路径上完全避免竞争,这个优化在低负载下看不出区别,但在节点初次同步大量区块的时候,能够把同步速度提升大概百分之三,百分之三听起来不大,但以太坊节点初次同步可能需要好几天,几个小时的缩短对用户来说体验差异巨大。

还有一次,我改了一个日志打印相关的代码,Go的标准log库在打印日志时默认加上时间戳,但Geth有自己的日志系统,会对每一条日志做格式化处理,我发现有一段代码在热路径上调用了标准库的time.Now函数,而Geth的日志系统里其实已经有一个缓存的时间变量可以直接复用,我把这个来源改掉之后,日志打印本身的延迟降低了一半,热路径上的每一点优化都会被放大,因为在全网的节点上,它会被执行无数次。

做这些贡献的过程中,我收获的不仅仅是PR被合入的满足感,更重要的是,我开始真正理解Go语言在并发场景下的细微特性,理解以太坊节点在不同网络状况下的行为模式,我甚至通过和社区成员的互动,学会了如何写更清晰的提交信息,如何设计更安全的代码结构,这些东西在学校里学不到,在公司里也未必有人会手把手教给你,但在开源项目中,你的每一份代码都会被最挑剔的眼睛审视,这种“被审视”本身就是最珍贵的经验。

我还想聊聊心态

很多人第一次向大型开源项目提交代码的时候会害怕,害怕被拒绝,害怕自己的代码写得不够好,害怕被社区里的人批评,这些恐惧很正常,我在提交第一次PR之前也犹豫了整整三天,反复看那段代码,反复怀疑自己是不是漏掉了什么,但后来我发现,大多数开源社区对待新人比你想象中友善得多,他们拒绝你的代码,通常不是否定你这个人,而是因为他们需要照顾整个项目的长期健康,那些严格的review和尖锐的问题,本质上是一种保护。

接受拒绝本身就是成长的一部分,被拒绝之后,你会反思,会改进,会变得更专业,我有一份PR被拒绝了两次,第三次才通过,第一次拒绝是因为我没有跑测试,第二次拒绝是因为我没有更新相关文档,直到第三次,我把所有细节都补齐了,才拿到approve,那次之后,我养成了一个习惯:每次提交代码之前,先问自己三个问题:测试跑过了吗?文档更新了吗?提交信息能解释清楚为什么改吗?

不要小看非代码贡献,很多人在最开始接触开源的时候,只盯着代码,但实际上,回答问题、写文档、翻译、组织社区、帮忙调试,这些都是极其重要的贡献,我认识一个朋友,他从来不改Geth的代码,但他每周都会在Discord上回答新手的提问,把自己调试网络故障的经验写成帖子,社区里很多人都认得他,信任他,后来他因为贡献突出,被邀请成为项目的正式维护者,这件事告诉我,开源不是只有代码,开源是人和人之间的协作网络。

还有一个很深的体会:开源的回报是长期的,你提交了一个优化,看起来只是帮项目省了一点点资源,但这份代码会被打包进下一个版本,被全世界的节点下载运行,成千上万台机器上,这个优化每秒钟都在起作用,你能真实地感觉到,你的工作正在被大规模复用,这种感觉很微妙,也很上瘾,它让写代码这件事从“完成需求”变成了“创造价值”。

最后回到我开始讲的那个小优化,一行代码,从循环里移到循环外,看似微不足道,但它改变了我对开源的理解,它让我意识到,开源贡献不是一种技术行为,而是一种社会行为,你在和一个跨国、跨时区、跨背景的团队共同维护一个复杂系统,你需要学会信任、学会沟通、学会对自己的每一行代码负责。

如果你也想开始开源贡献,我的建议很简单:找一个你日常使用的工具,读它的源码,读到你觉得可以改的地方,就动手改,别怕被拒绝,拒绝是常态,但每一次被拒之后,你都会离“真正的贡献者”更近一步。

改一行代码就够了。