写点什么

瞧不上 C++ 和 D 语言,国外程序员将 5.8 万行代码迁移到 Jai 语言,到底图什么?

  • 2022-12-02
    北京
  • 本文字数:3633 字

    阅读完需:约 12 分钟

瞧不上 C++ 和 D 语言,国外程序员将 5.8 万行代码迁移到 Jai 语言,到底图什么?

AI 大模型超全落地场景&金融应用实践,8 月 16 - 19 日 FCon x AICon 大会联诀来袭、干货翻倍!

本文中,我将向大家分享自己把游戏开发成果移植到 jai 语言的经历。我的游戏之前主要是用 D 和部分 C_++ 编写的,总代码有 58620 行(不包括库)。其实这事我已经筹划了很久,还专门记录下了最初的期望、移植过程和最终结果。下面,就请大家随我一道回顾这段历程。


为什么要移植


很多朋友可能好奇,为什么会有人要大费周章把这么些代码移植成另外一种语言?把项目做完,之后再用新语言不好吗?我当然有自己的考虑,主要基于以下几点:


  • 现状让我的日常工作非常痛苦

  • 我有称手的系统移植工具,所以移植过程应该会比较顺利

  • 对我来说,Jai 似乎比 C++ 或者 D 更有吸引力


为什么不用 C++


网上关于 C++ 缺点的讨论已经很多,所以这里不再赘述。简而言之,C++ 几十年发展下来积累了太多错误决策,而且永远都摆脱不掉了。它的标准库堪称灾难,代码几乎不可能在交给他人后顺利运行。而且不知道为什么,C++ 每次新增功能都有坑。所以 C++ 的使用感受确实很差,时时刻刻在折磨着我,而且这个项目的发展方向在我看来也有问题。总之,我想尽快离开 C++ 生态系统。

为什么不用 D

我的游戏开发之旅始于 2019 年,当时我已经感受到 C++ 的问题了,但并不知道哪种语言更好。于是我选择了 D,理由如下:它很像 C++,只是去掉了不好的部分。但很遗憾,这只是我的一厢情愿,D 在很多方面都跟 C++ 一个德行。


必须承认,D 跟 C++ 比确实有一些优势,比如更强大的元编程、无需标头、没有未初始化的值等。但很遗憾,这些优点根本就抵消不掉现实缺陷。


我在 Windows 上的两种 D 编译器(dmd 和 ldc2)之间反复横跳了四年,最后发现至少在 Windows 平台上,D 语言的状态也就是个业余项目、往好听了说也是极不成熟的水平。很难相信这是种已经发展了 20 多年的语言。截至目前,我不建议任何人在 Windows 上用 D 开发严肃项目,甚至还不如继续用 C++。从我亲身经历的问题来看,目前最大的麻烦是 Windows 上的调试信息完全 损坏:


  • 在 90% 的情况下,this 指针会缺失或出错

  • 函数堆栈上的变更经常部分 / 完全丢失或出错

  • 有时会报告变量值错误,但却没有任何其他可见的问题

  • 静态 foreach 扩展处理不当,令调试工具无所适从

  • minxins(相当于 D 中的宏)生成的调试信息会令调试工具找不到正确文件(只能按步进行反汇编)

  • 在 Visual Studio 中将指令指针移回上一行,经常导致程序在执行下一条指令时崩溃在 D Language Code Clulb 讨论版上,我看到“DMD 以往曾经出过很多关于调试信息的问题”,但号称正在处理中。可很遗憾,ldc2 也根本没好到哪去。而且除了调试之外,元编程这边也有不少缺点和问题:

  • 不同的编译器阶段会诡异地相互影响,导致元编程出现意外,引发误导性错误

  • ldc2 的编译速度非常慢,但我别无选择,因为 dmd 有 bug

  • D 提供所谓 betterC 模式,其中禁用垃圾收集;但在使用此模式时,标准库不编译而且元编程也会受到严重阻碍

  • 说明文档缺失

  • 还有其他几十个更具体的问题


总而言之:虽然 D 在某些方面确实比 C++ 强点儿,但其他地方的问题反而更多,导致使用体验痛苦万分。其中最大的问题就是糟糕的调试信息和极具破坏力的垃圾收集机制。我承认,我只是想把 D 当成改进版的 C++ 来用,而 D 的开发者并不认同这种理解。所以 D 不适合我,而且我现在根本就不敢信任它的调试器。


为什么选择 Jai


种种遭遇,把我引向了 Jai。这是 Jonathan Blow 从 2014 年开始开发的一种语言,而且直到 2019 年 12 月才向编译器敞开大门。目前封闭测试仍在进行中,我也是约两个月前受邀体验的一员。Jai 的诞生源自 Blow 对 C++ 的失望。而且跟 D 不同,Jai 确实朝着我所认可的、能够对 C++ 做出有意义改进的方向前进。在我看来,Jai 的最大优势有二:更快的编译速度,还有以不受限制的编译时执行进行元编程。


值得注意的是,这里讨论的可不是编译速度提高 20%、或者元编程功能选项略有增加之类不痛不痒的小改进。Jai 的编译速度提高了 10 到 100 倍,而且能在编译期间执行任何操作。特别是与编译时编译器 API 相结合的元编程,已经带来了具有深远意义的影响:例如消除对构建系统的需求,也摆脱了对复杂的非启发式自定义检查的依赖。除此之外,Jai 还对 C++ 做出了其他改进,例如更好的默认值、更简单的语法、更实用的标准库、命名函数参数、上下文、using 等。这就让我有了信心,打算通过从 D 移植到 Jai 让自己的游戏获得以下收益:


  • 将编译时间从现在的 60 秒左右降低到 5 秒以内,最好能达到 1 秒上下

  • 调试器终于能用了 

  • 用 Jai 代码替代了 build-script

  • 用元编程引入自定义编译检查,借此捕捉更多错误

  • 用更简单的命令式代码替代复杂的元编程代码

  • 提供一系列语法改进,减少了代码中的噪声

  • 删除了以往使用多种语言时无法避免的重复部分 ##

    为什么不考虑其他语言


的确,我明确不想用的只有 D 和 C++,而略感兴趣的是 Jai……那为什么不试试别的语言呢?


最无法回避的选项应该就是 Rust 了。它风头正劲、社区活跃,但我还是感觉 Rust 在设计权衡方面有点问题。支持 Rust 的开发者们似乎有种“内存安全是第一要务”的集体心态。没错,很多问题都源自内存安全问题,所以我大体能够理解这样的判断。但也有很多对于安全和质量要求没那么极端的软件需求。


比如,我相信如果 C 和 C++(基本就是公认的「最不安全」语言)能够去掉零终止字符串、默认初始化值和适当的指针 + 长度类型,那就足以用边界检查取代 90% 的指针算法、解决临时内存管理的需求了。另外,我觉得很多人在追求“安全”代码时,其实是忽略掉了软件漏洞层出不穷的根本原因:文化上对于复杂性的容忍,甚至是鼓励 14。总之,我不愿意忍受 Rust 那漫长的编译时间,也不太认同它的文化定位。跟 Rust 不同,jai 就很关注如何控制复杂度,这一点更符合我自己的文化判断。


还有其他一些人气稍逊的选项,例如 Zig。我只能说它们可能也有巨大的潜力,但我不太相信这些会是正确的选择。不是好或者不好,只是没那么强的吸引力。


如何移植


刚开始,我还在心里给自己鼓劲、祈祷移植过程能顺利完成。之所以比较乐观,是因为我在游戏中设置了两套有助于移植的系统:


  • 游戏会将游戏会话的输入(HID、加载文件、网络等)记录到文件内并稍后重播。在重播时,记录的输入输出确定性能让游戏循环达到完全相同的状态,精确无误。

  • 游戏在执行过程中的各个点位上,都会对游戏状态进行哈希处理,并将相应的哈希值保存在不同文件当中。这样在重播时,文件内容即可用于检查重播是否跟原始执行完全匹配。


这两项功能间有一些细微差别,但对整体运行影响不大。依托这些功能,我的移植计划如下:


  • 将一小段代码由 D 或 C++ 复制到 jai,而后编译。

  • 调用新的 jai 代码,替代旧有 D 或 C++ 代码。

  • 重播录制的游戏会话。

  • 如果重播发生分歧,则说明移植引入了 bug,修复此 bug。

  • 如果重播无分歧,则移植成功,继续下一步。


这种方法的关键在于:


  • 是不是所有引入的 bug 都会导致游戏状态发生显著分歧?

  • 代码能否以较小的增量进行移植,以便易于知晓 bug 存在、找到 bug?


第一个问题,取决于状态哈希覆盖到多少代码。有些代码需要知晓游戏是否正在重播,这些部分的内部行为会有所不同,因此无法得到有意义的哈希值。例如,写入文件功能会在重播时丢弃一切数据,因此如果移植后的写入文件中出现了 bug,就会被哈希过程注意到。幸运的是,大部分代码并不属于这一类。最初的哈希在代码库中极少被用到,但最近我开始将其扩展到插入动态数组的过程,例如记录动态数组的大小和容量。


如此一来,当有 bug 导致动态数组的插件会改变游戏逻辑时,问题就会被及时注意到。因为我代码中的几乎所有功能都是靠动态数组实现的,所以即使是在任何庞大而复杂的数学算法当中,每一点微波的行为变化都能引起注意。


第二个问题则属于经典编程问题:你的代码解耦程度到底有多高?这一点非常有趣,因为我得把代码库里的所有偶发复杂性元素找出来。首先就是模板函数:它们无法直接移植,因为函数定义和调用站点必须在同一编译器之内,才能让模板正常起效——除非对模板进行手动实例化。我的代码库里有不少模板化代码,但它们跟容器和序列化没什么紧密关联,所以我觉得这应该不会惹出太大的麻烦。


继续推进


下面来点更直观的统计数据和图片吧。先来看我这代码库的当前状态:



总体来说,这里有 45701 行 D 代码和 12919 行 C++ 代码,总共 58620 行。编译时间如下:



在调试模式下,ldc2 的编译过程大概需要 3 分钟,最高占用 8 GB 内存。这时候如果打开浏览器,那我这台 16 GB 内存的笔记本电脑就会进入满负荷运行。发布模式内存占用量更大,为 11.5 GB。




如果一切顺利进行,那在两张图中,一切现有色块都应该会被新色块取代。能成功吗……


最后,我想用数字来解释移植的收益,特别是我当初的预期错得有多离谱:


  • 我预计整个过程需要 160 个小时(每周 40 小时,共耗时一个月)。但我这个估计差得太多了,很可能根本用不上一个月的时间。

  • 我希望编译时间能从 1 分钟左右下降到最多 5 秒,能到 1 秒上下最好。至于移植后的最终结果如何,我将持续保持更新。


原文链接:

https://www.yet-another-blog.com/porting_the_game_to_jai_part0/

公众号推荐:

AGI 概念引发热议。那么 AGI 究竟是什么?技术架构来看又包括哪些?AI Agent 如何助力人工智能走向 AGI 时代?现阶段营销、金融、教育、零售、企服等行业场景下,AGI应用程度如何?有哪些典型应用案例了吗?以上问题的回答尽在《中国AGI市场发展研究报告 2024》,欢迎大家扫码关注「AI前线」公众号,回复「AGI」领取。

2022-12-02 16:214795
用户头像
李冬梅 加V:busulishang4668

发布了 874 篇内容, 共 444.8 次阅读, 收获喜欢 1044 次。

关注

评论

发布
暂无评论
发现更多内容

DELETE Statement,懂你不容易

TiDB 社区干货传送门

TiDB 底层架构

Tidb灾难恢复演练-多副本丢失

TiDB 社区干货传送门

故障排查/诊断

TiDB 赋权问题

TiDB 社区干货传送门

故障排查/诊断

记一次TiDB优化

TiDB 社区干货传送门

性能调优

悲观事务加锁验证

TiDB 社区干货传送门

管理与运维

TiDB 在网易游戏的应用实践

TiDB 社区干货传送门

实践案例

TIDB 入门运维基础视频教程(一)-- 快速体验

TiDB 社区干货传送门

安装 & 部署

PD 关于tso 分配源代码分析

TiDB 社区干货传送门

TiDB 底层架构

排序算法总结

乌龟哥哥

7月月更

【TiDB 最佳实践系列】如何高效利用 Grafana 监控分析 TiDB 指标?

TiDB 社区干货传送门

监控

TiDB 4.0 新特性也太爽了吧

TiDB 社区干货传送门

版本测评

从一个简单的Delete删数据场景谈TiDB数据库开发规范的重要性

TiDB 社区干货传送门

故障排查/诊断

TiDB系统调参实战经验

TiDB 社区干货传送门

性能调优 实践案例

TiDB+FLINK 实时计算

TiDB 社区干货传送门

实践案例

【SOP 系列】TiDB 使用 SOP 最全合集

TiDB 社区干货传送门

TiDB 底层架构

TiDB升级5.0.2有惊喜

TiDB 社区干货传送门

版本测评

一条 like 条件的慢 SQL 语句优化

TiDB 社区干货传送门

管理与运维

TiDB 记录日志原理解读

TiDB 社区干货传送门

TiDB 底层架构

当大数据架构遇上 TiDB

TiDB 社区干货传送门

实践案例

TSO 时间戳转换为自然时间

TiDB 社区干货传送门

实践案例

前端食堂技术周刊第 44 期:Bun、Vue.js 挑战、React 状态管理的新浪潮、Can I DevTools、函数式编程

童欧巴

Vue React Bun

TiDB 目录结构分析

TiDB 社区干货传送门

实践案例

PD 关于ID分配的源码分析

TiDB 社区干货传送门

TiDB 底层架构

TiDB GC 之原理浅析

TiDB 社区干货传送门

TiDB 底层架构

Spring IOC II

武师叔

7月月更

SpringBoot核心应用第一弹

Java学术趴

7月月更

价值几十万的 TiDB优化

TiDB 社区干货传送门

实践案例

Spring&SpringBoot 源码笔记整理 |Bean 的加载流程一

自由

Spring5源码解析 7月月更

TiFlink: 使用 TiKV 和 Flink 实现强一致的物化视图

TiDB 社区干货传送门

实践案例 TiDB 底层架构

一个联合索引使用问题以及优化方案

TiDB 社区干货传送门

管理与运维 故障排查/诊断

TiDB 5.1 发版,打造更流畅的企业级数据库体验

TiDB 社区干货传送门

新版本/特性发布

瞧不上 C++ 和 D 语言,国外程序员将 5.8 万行代码迁移到 Jai 语言,到底图什么?_编程语言_Simon van Bernem_InfoQ精选文章