速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

我是如何把 5 万行 C++ 代码移植到 Go 的?

  • 2019-04-13
  • 本文字数:2795 字

    阅读完需:约 9 分钟

我是如何把5万行C++代码移植到Go的?

Go 语言的创始人之一 Rob Pike 曾表示,他希望 Go 能够被 C++程序员所接受,但结果差强人意。最近,在作者就职的 HFT 公司里,一个团队成功地把一些对速度不太敏感的基础设施代码从 Python 移植到了 Go,这也促使他们决定尝试用 Go 对复杂冗余的 C++服务端程序进行重构,这些代码有 5W 行之多,并且对吞吐量有一定的要求。

这个服务端程序使用了跟公司核心交易软件相同的技术和库,不同地是交易软件对系统的延迟更敏感,几乎每一微秒都很重要,而 C++服务端并不需要这种程度的性能。

因此,使用 Go 自带的调度程序完全可以满足要求,没有必要使用交易系统实现的超优化 C++框架,虽然损失了一些性能但获得了更好的可维护性。需要一提的是,本文作者负责了整个代码的重写工作。

前言

从商业角度来看,这个项目是成功的:重写工作提前完成;性能在可接受的范围之内;并且整体代码量不超过 1W 行(代码量的剧减主要是因为重写团队删除了一些过时的或者不需要的特性)。但从开发者的角度来看,作者认为结果并不是最优的。Go 并不支持参数多态,作者因此使用了两到三倍的代码来实现类似功能。其中一部分是为了保障类型安全:Go 强制开发者在类型修饰和类型安全之间做出取舍,作者选择了一个比较均衡的实现。总的来说,如果需要一般的类型安全,那么相对少的代码就可以实现,而如果需要更好的类型安全,则需要更多的代码。


接下来让我们对比一下 Go 语言的优缺点。

优点

Emacs 开发平台

借助自动完成、跳转到定义、保存时的错误检查、智能重构和 GoTest 集成等插件,Emacs 成为了 Go 语言环境下最好的 IDE 工具。另外,它也可以很方便地通过 Elisp 进行定制和扩展。如果你本人恰好是 Emacs 的爱好者,这绝对是一个大大的加分项。

Goroutines(协程)

Go 实现了基于消息传递的并发,作者认为这是最简单的并发形式,使用也超级方便。通过将 GOMAXPROCS 设置为 1,Go 还允许开发者通过使用与并发代码完全相同的方式来编写并行/异步代码。与其它提供内置轻量级线程调度器的语言 Erlang/Elixir 和 Haskell 相比:前者缺乏静态类型,后者在实际开发中很少被管理人员采用。

没有继承

在很多情况下,基于继承的 OO(面向对象)是一种反模式,这些冗余和模糊的代码几乎没有什么好处,Go 则直接取消了这类代码。这有可能也是 Rob Pike 等人设计 Go 的初衷:谷歌内部有一大堆类似于企业版本 Fizzbuzz 的 Java/C++代码,他们希望能从这些代码中彻底解放出来。也就是说,尽管在旧的 C++服务端遗留代码中使用继承是合理的,但最好还是使用更现代的风格来重写代码,而且重写过程也并不复杂。

更好的可读性

Go 代码更易于阅读和理解。相比之下,很多 C++代码需要几个小时才能完全理解。Go 本身也促使开发者编写可读的代码:这种语言完全避免了下面这种自做聪明的情形


“嘿,这篇论文(基本上没人读得懂)中的>8=3 运算符可以让我节省 10 行代码,我最好把它写进代码里,我的同事也不难理解这行代码,因为它的意思已经在类型签名中很清楚地表达出来了(反正我是没看懂):(PrimMonad W, PoshFunctor Y, ReichsLens S) => W Y S((I -> W) -> Y) -> G -> Bool "。

简单而规范的语法

当我们需要将一个封闭函数的名称添加到每个日志字符串的开头时,如果使用 Emacs,一个简单的 regexp find-replace(正则表达式)命令就可以实现,而对于更复杂的语言则需要使用解析器。不论是通过 Emacs 宏或者是 Go 模板,简单的语法可以更容易地生成代码。


Emacs+Go==参数多态:我们可以使用 Emacs 宏来加速生成 Go 所需要的"复制粘贴",而且,如果函数编写正确,那我们也可以用 regex 命令来更新所有的"复制粘贴"函数。这样,我们就可以很容易地更新 fooInt、fooFloat 和 fooDouble 等函数,对比支持参数多态的语言对 foo 函数的更新,整个过程没有什么太大区别。这样做的缺点是,虽然 Emacs 宏和 regex 命令可以编写和修改 Go 代码,但它仍然不如真正的多态实现那样简洁和易读;而且对于不熟悉 regex 以及可扩展编辑器(Emacs)的人来说,维护同样也不容易。

有效的内置模板

通过 Go 的文本/模板包,我们可以很容易地生成新代码。它还允许开发者在生成代码时使用 IO:例如,有一个同某些特定服务交互的库,它通过 XML Schema 生成。如果能够用不同的函数来生成不同的数据类型,那么就可以保证代码的类型安全。


在 C++中,IO 不能在编译时执行,因此不能使用上述模式来生成代码。允许编译时使用 IO 的语言有:


  • F#,通过 TypeProviders 实现。

  • Idris,也使有 TypeProviders。

  • Lisp,可以在宏中执行 IO。

  • Haskell,它有一个编译期运行的函数 IO -> Q。

  • D,编译时可以使用“import”来读取文件。

  • Nimrod,有特殊的函数实现。

  • Elixir 或 Erlang,可以通过宏执行任意的 IO。

  • Rust,可以使用函数 libsyntax 在编译时执行任意的计算和 IO。

缺点

斯德哥尔摩综合征

前面已经提到,在允许使用 IO 的特性上,使用模板生成 Go 代码要比用 C++元编程好得多,而 C++元编程在这里显然是多余的,因为完全可以用另外一种可以支持 IO 的程序语言来生成代码。

没有实现参数多态

尽管很多人认为这在实践中并不是一个问题,但在这里,它是一个很严重的问题。如果把新的 Go 代码再移植回 C++的话,考虑到 C++的函数多态和类型多态,代码量可能会减少到目前的一半,并且具有更好的类型安全。如果用 Haskell 重写的话,代码量会更少,而使用 Clojure 的话,代码量有可能控制到 1000 行以内,当然这些代码可能很难被调试或维护。

牺牲了类型安全

针对服务器处理的各种 protobuffer messages(协议缓冲消息),我们使用了扩展属性的方式,作者最初打算为每一种消息设置一种扩展属性,这样 FooExtensionAttribute 就不能用在 Bar 函数上。Go 并没有实现参数多态和泛型,这意味着将会产生大量的重复代码,所以最终只使用了一种 ExtensionAttribute,并且类型系统也没有检查它是否用于扩展合适的消息。

二进制文件太大

如果使用代码来生成类型安全的 API,并确保每种数据类型都有明确的类型访问器和诸如此类的东西,则很容易生成超过 10W 行的 Go 代码以及 30MB 以上的二进制文件,编译时间也会更长。在这种情况下,一般会超过 10 秒。当然,这不是一个很严重的问题,因为我们可以把代码编译成静态库,这只需要一次,之后就可以通过静态链接来访问了。

内核兼容性有待提高

很多时候由于各种无奈的原因,需要把代码部署到一个旧内核上。而且,如果这个内核不支持最新的 Go 版本,就不得不换到一个旧的、很慢的 Go 版本,这多少有些令人沮丧。

结语

Go 语言是一把双刃剑:它禁止一切复杂的抽象,不管是优秀的抽象亦或是很差的抽象。如果你和你的同事正在使用很糟糕的抽象,那切换到不能使用抽象的 Go 语言自然很好,反之亦然。当然这也要取决于判断抽象好坏的标准。


查看英文原文


https://togototo.wordpress.com/2015/03/07/fulfilling-a-pikedream-the-ups-of-downs-of-porting-50k-lines-of-c-to-go/



2019-04-13 14:0016572

评论 3 条评论

发布
用户头像
我心中的编程语言尝鲜指数:
第一梯队:Rust(系统编程),Julia(服务器端开发及数据领域),Dart(移动开发)
第二梯队:C++1x(系统编程),Go(服务器端开发),Python(服务端开发及数据领域),Kotlin(移动开发),Swift(移动开发)
第三梯队:C/传统C++(系统编程),Java(服务器端开发及Android开发),PHP(服务器开发)
2019-04-22 12:21
回复
用户头像
试试Rust吧,效果会更好!
2019-04-21 14:21
回复
用户头像
希望哪一天,go能支持泛型和多态
2019-04-15 11:08
回复
没有更多了
发现更多内容

在人工智能冲击下,IT部门的生存价值在哪里?

FN0

AIGC

蚂蚁集团自动化混沌工程 ChaosMeta 正式开源

ChaosMeta

高可用 混沌工程 故障演练 kubernetes 运维 混沌测试

一种实现Spring动态数据源切换的方法 | 京东云技术团队

京东科技开发者

spring aop 企业号 6 月 PK 榜 数据源切换

如何评估大型语言模型(LLM)?

Baihai IDP

人工智能 深度学习 大模型 白海科技 大模型评估

一文读懂ChatGPT的工作原理:大语言模型是个啥?它到底咋工作的?

禅道项目管理

#人工智能 ChatGPT AI 2022

Airtest图像识别测试工具原理解读&最佳实践 | 京东云技术团队

京东科技开发者

图像识别 移动开发 UI自动化测试 企业号 6 月 PK 榜 Airtest

AI+电力、大模型主题人工智能师资培训班重磅招募中

飞桨PaddlePaddle

人工智能 百度 paddle

可观测性最佳实践 | 警惕!未知的风险正在摧毁你的系统

观测云

可观测性 运维监控 观测云 云原生可观测 可观测性用观测云

华为云CodeArts Build快速上手编译构建-进阶玩家体验

华为云PaaS服务小智

云计算 编译 开发 华为云

海外交友源码平台搭建:基础功能的实现(一)

山东布谷科技

软件开发、 源码搭建 海外市场 语音交友源码

软件测试/测试开发丨Pytest结合数据驱动-CSV

测试人

程序员 软件测试 自动化测试 csv pytest

高性能网络 SIG 月度动态:联合 IBM 就 SMC v2.1 协议升级达成一致,ANCK 率先完成支持

OpenAnolis小助手

开源 ibm 高性能网络 anck 龙蜥sig

使用华为云AstroZero,不用一行代码,制作端午节加班申请模板

华为云PaaS服务小智

云计算 零代码 华为云

浅谈API安全

权说安全

API 安全

漫谈 SAP 产品里页面上的 Checkbox 设计与实现

汪子熙

SAP 前端设计 思爱普 6 月 优质更文活动

基础设施SIG月度动态:ABS新增ISO、VHD镜像构建,自动热补丁制作流程正式上线

OpenAnolis小助手

镜像 基础设施 龙蜥社区 sig abs

模型当道 开源聚力|2023开放原子全球开源峰会开源大模型分论坛圆满收官

开放原子开源基金会

开源 大模型 开放原子全球开源峰会 开放原子

手牵手带你实现mini-vue | 京东云技术团队

京东科技开发者

Vue 数据绑定 vue2 企业号 6 月 PK 榜 双向数据绑定

Vue3中常用的Composition(组合)API-watch(监视)函数

不觉心动

6 月 优质更文活动

300行代码模拟cdn访问过程

蓝胖子的编程梦

CDN DNS CDN加速 CDN技术 #DNS

随机2D形状周围层流预测!基于飞桨实现图形神经网络

飞桨PaddlePaddle

人工智能 百度 飞桨

Java 内存与缓存管理:应对大数据场景的优雅高效策略

xfgg

Java 6 月 优质更文活动

细说敏捷测试-敏捷实战中的探索 | 京东云技术团队

京东科技开发者

敏捷开发 测试 敏捷测试 企业号 6 月 PK 榜

Java 中优雅的 RESTful API 设计:实现高效且易维护的接口

xfgg

Java RESTful API 6 月 优质更文活动

vivo 游戏黑产反作弊实践

vivo互联网技术

游戏黑产 游戏礼券

海南正规等级保护测评单位有哪些?叫什么名字?

行云管家

等保 等级保护 海南 等保测评单位

IT自动化运维工具用哪款?需要考虑哪些因素?

行云管家

IT运维 自动化运维 IT自动化运维

AI关于AI民主化的高见

FN0

AIGC

TBB 开源库及并发 Hashmap 的使用

KaiwuDB

KaiwuDB TBB开源库 Hashmap使用

详解4种模型压缩技术、模型蒸馏算法

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号 6 月 PK 榜

我是如何把5万行C++代码移植到Go的?_编程语言_logicchains_InfoQ精选文章