不喜欢 D 和 C++,程序员将 58000 行代码移植到 Jai 语言?

f42c1a4290e1fa655a6db8ac6bb1f904.gif

摘要:将已有的上万行代码迁移至另一种编程语言,从来就不是一件容易决定的事情,而本文作者却信心满满地要将 5.8 万行代码全部用另一种不那么主流的语言重写,这是为什么呢?

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

声明:本文为 CSDN 翻译,未经允许禁止转载。

作者 | Simon van Bernem       

译者 | 弯月    责编 | 郑丽媛

出品 | CSDN(ID:CSDNnews)

在这篇文章中,我将分享把一款正在开发的游戏移植到 Jai 语言的经过。游戏本身主要是用 D 和 C++ 编写的,总共有 58,620 行代码(不包括库)。

332a4d0020808b2c0274297d87867c82.jpeg

9f4b681ec52370d2e01569a945fbd610.png

原因

你可能想问,为什么要将如此大规模的程序移植到另一种编程语言?你完全可以等到新项目再用新语言嘛!

我之所以移植这些代码,原因主要有以下几个:

  • 目前这些语言给我的日常工作带来了无尽的痛苦。

  • 我有合适的系统来帮助我移植代码,所以我认为此次移植能够顺利进行。

  • 对于我来说,Jai 似乎比 C++ 或 D 更具吸引力。

最重要的原因是:我喜欢 Jai!

bcaa3b93d9990510281ad2be67ed4678.png

为什么放弃 C++

网上有很多文章诉说了 C++ 的缺点,所以我不打算在此赘述。简单来说,C++ 几十年的发展积累了很多错误决定,我们没有任何方法去摆脱它们。标准库是一场灾难,使用其他人的代码也非常困难,而且不知何故,C++ 每次添加的新功能都有陷阱。

虽然 C++ 的这些缺点也不至于糟糕到让人避之不及,但它确实给我带来了很多痛苦。此外,这些年来 C++ 的发展似乎不尽如人意,我不觉得这门语言会越来越好,所以我还是想逃离 C++ 生态系统。

3987fca220f0558aeadd7820890df334.png

为什么放弃 D 语言

2019 年,当我开始开发这款游戏时,已决定放弃 C++,但我不确定应该选用何种语言。最终,我选择了 D 语言,因为这门语言与 C++ 类似,但没有 C++ 的那些缺点。然而不幸的是,事实证明 D 语言也有一些 C++ 中不存在的缺点。

虽然 D 语言有一些优点,比如更强大的元编程、不需要头文件、没有未初始化的值等,但相较于缺点而言,这些优点不值一提。

我在 Windows 的两款 D 编译器(dmd 和 ldc2)之间来回折腾了 4 年之久,到头来却发现在 Windows 上编写 D 代码,只适合个人的业余项目或早期不成熟的软件,该语言完全不像有 20 多年的发展经历。

就目前的情况来看,我不建议任何人使用 D 语言在 Windows 开发正式项目。相较而言,继续使用 C++ 才是更好的选择。根据我多年的经验来看,在 Windows 编写 D 代码所面临的最大问题在于,其调试信息千疮百孔:

  • 90% 的情况下不会给出 this 指针,或给出错误的 this 指针;

  • 函数堆栈上的变量经常不完整、丢失或有误;

  • 变量的值有时会报告错误,却看不到任何其他问题;

  • 静态 foreach 扩展的处理不当,甚至会导致调试器紊乱;

  • mixins(相当于 D 语言的宏)生成的调试信息会导致调试器找不到正确的文件(因此你需要逐步反汇编);

  • 在 visual studio 中,将指令指针移到上一行常常会导致程序在下一条指令上崩溃。

网上有人告诉我,一直以来 DMD 的调试信息就存在很多质量上的问题,但不幸的是,上述大部分问题不仅限于 DMD,Windows 的两个编译器都有这些问题。除了调试之外,元编程的核心部分还存在其他问题和缺点:

  • 不同的编译阶段以奇怪的方式交互,常常导致元编程出现意外,同时还会产生具有误导性的错误;

  • ldc2 的编译速度非常慢,但有时这款编译器是唯一的选择,因为 DMD 有 bug;

  • D 提供了一种 betterC 模式,其中包括禁用垃圾收集。然而,在使用这种模式时,标准库不会被编译,而且元编程也会遇到重大问题;

  • 缺少文档;

  • 此外,还有一堆小问题。

总之,虽然从某些方面来看,D 确实比 C++ 略胜一筹,但其他方面的小问题非常多,累积起来导致使用 D 语言编程也非常痛苦。糟糕的调试信息和垃圾收集的需求很致命,我的整体感受是,D 的创始人对 C++ 的看法似乎与我截然不同。我只是希望改进 C++,而不是用一些 C++ 的问题来换取 D 的其他问题。现在我对调试器有严重的信任问题。

17445c7b5812b1bb9afcb15fc532c206.png

为什么选择 Jai

Jai 是 Jonathan Blow 于 2014 年开发的一款编程语言,而编译器一直到 2019 年 12 月才开始内测。如今封闭测试仍在进行中,大约两个月前,我应邀参加了这项测试。

Jai 的设计初衷也是希望改进 C++,但与 D 语言不同,Jai 正在对 C++ 做出有意义的改进。在我看来,最重要的改进包括编译速度更快,以及允许通过无限制的编译时执行来实现元编程。

请注意,这里我所说的编译速度提升可不止 20%,而是 10~100 倍;而且你能在编译期间执行任何操作,不仅限于元编程。尤其是,元编程与编译时编译器 API 的结合使用具有深远的影响,例如你不再需要构建系统,或启用复杂的自定义检查。除此之外,Jai 还对 C++ 进行了其他方面的改进,比如更好的默认值、更简单的语法、更实用的标准库、命名函数参数、上下文、using 等等。我希望我的游戏从 D 移植到 Jai,能够获得以下提升:

  • 编译时间从现在的 60 秒减少到 5 秒以下,争取能缩短至 1 秒左右;

  • 调试器能够正常工作;

  • 用 Jai 代码替换构建脚本;

  • 使用元编程引入自定义编译检查,以抓取更多错误;

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

  • 各种语法改进,减少代码中的繁琐部分;

  • 删除使用多种语言时不可避免的重复。

我希望通过这篇文章记录我的期望,将来可以回过头来检查有多少期望真的实现了。

97f27cd9b87b080ad7c1e2d3bba598f5.png

为什么不是其他语言

如上所述,我不喜欢 C++ 和 D 语言,而 Jai 看起来很不错。那么,其他编程语言呢?似乎 Rust 也是一个很好的备选。这门语言风头正盛,而且还有一个热心的社区,但我个人认为 Rust 并没有做出正确的权衡。

它的支持者都是唯“内存安全”是论者,考虑到如此多的漏洞都是内存安全引发的,我可以理解这种心态,但他们忽略了其他高安全性、高质量的方法。例如,我相信如果 C 和 C++ 没有零终止字符串、默认初始化值,那么大部分漏洞都不会存在;再加上合适的指针+长度类型,就可以用边界检查代码取代 90% 的指针计算;然后再建立一种文化,不鼓励大家自己想办法自行管理内存。

除此之外,我认为,我们在寻找“安全的”代码时完全忽视了我们拥有如此多漏洞的最主要的因是,我们的文化对复杂性的容忍,甚至是鼓励。总之,忍受 Rust 慢吞吞的编译并接受借用检查器是一种极端的解决方案,并没有解决最重要的问题,这是一个文化上的问题。另一方面,Jai 非常在意复杂性,并努力建立正确的文化。

对于其他不太受欢迎的语言,例如 Zig,我只能说虽然它们可能具有巨大的潜力,但我并无法相信它们是正确的选择。我不是说这些语言不好,只是它们不适合我。

02fca86f6891f462ee95cea7c6fd4f5a.png

移植方法

在本文开头,我曾说过我认为此次移植能够顺利进行,原因是我的游戏中有两个系统,对此次移植有很大的帮助:

  • 我的游戏可以将游戏过程中的输入(HID、加载的文件、网络等)记录到一个文件中,之后进行回放。在回放的过程中,系统可以将记录下来的输入传递给游戏循环,从而重现一模一样的游戏状态。

  • 玩游戏期间,系统可以在各个时间点,对游戏状态进行哈希处理,并将这些哈希值保存到不同的文件中。在回放整个游戏过程时,系统可以利用这些文件检查游戏的状态是否与原来匹配。

实际的功能和上面的描述有一些细微的差别,但不会影响整体的逻辑。根据这些特性,我的移植计划如下:

  • 将一小段代码从 D 或 C++ 复制到 Jai,然后编译;

  • 调用这段 Jai 代码;

  • 回放录制的游戏会话;

  • 如果回放出现分歧,则说明移植引入了 bug,修复 bug;

  • 如果回放没有出现分歧,则说明移植成功;

  • 重复以上操作。

关于该方法是否有效,我需要考察两个关键问题:

  • 移植引入的 bug 是否会导致游戏状态出现明显的分歧?

  • 能否以少量、渐进式的方式移植代码,这样在得知存在 bug 时,更容易找到bug?

第一个问题取决于状态哈希覆盖了多少代码。一部分代码需要判断游戏是否正在回放,这部分代码在回放时有不同的行为,因此无法完成真正的哈希处理。例如,写入文件的功能在回放时会直接丢弃所有数据,因此如果移植在写入文件的代码时引入 bug,则不会被哈希处理注意到。幸运的是,大多数代码不属于这一类。

最初,只有很小一部分代码使用了哈希处理,例如物理模拟,但最近我设法进行了扩展,在向动态数组插入数据时,用哈希来记录其大小和容量。这意味着,插入动态数组的代码中的 bug 很快就会被发现。由于动态数组的使用在我的代码中随处可见,所以对于庞大且复杂的数学算法来说,即便是一个很小的变化,也能带来立竿见影的效果。

第二个问题是一个经典的编程问题:代码的解耦性如何?这个问题非常有趣,因为在移植的过程中,我将亲眼目睹我的代码库中究竟封存了多少不为我所知的复杂性。一个明显的问题是模板函数,这些代码无法直接移植,因为函数的定义和调用必须在同一个编译器中,模板才能发挥作用,除非你手动实例化模板。我的代码库中有大量的模板化代码,但大多不依赖于容器或序列化,所以我希望不会引起太大的麻烦。

95751a68fc66d3a82381b77aacd82681.png

移植过程

下面这张图是移植前的代码库状态:

046cf18f52a71a61c93d1d3c4841cc93.png

整个代码库有 45,701 行 D 代码和 12,919 行 C++ 代码,总共 58,620 行。 编译时间如下:

2a2bf5404036657244e2009d5f439691.png

在调试模式下,ldc2 需要 3 分钟才能完成编译,内存使用量峰值约为 8GB,如果打开浏览器,我笔记本电脑的 16GB 内存很容易就饱和了。发布模式更糟糕,约为 11.5GB。

为了记录移植进度,我绘制了如下代码库的示意图:

ad7e5407093d04767d536036d8e08345.png

d72826270853f8b50e6ad6b111083617.png

如果一切按计划进行,上面两张图中的颜色都会改变,我非常期待!

最后,我来说一下我期待的效果:

  • 整个移植的过程需要 160 小时,每周工作 40 小时,一共需要一个月。

  • 编译时间从 1 分钟缩减到 5 秒以下,理想值为 1 秒左右。

7feefde28275de8184a019d35daf847b.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

传统微服务框架如何无缝过渡到服务网格 ASM

背景 软件技术的发展历史,从单体的应用,逐渐演进到分布式应用, 特别是微服务理念的兴起,让大规模、高并发、低延迟的分布式应用成为可能。云原生时代下,微服务框架本身也在不断地进化和迭代演进。 微服务框架一般会涉…

基于 K8s 的交付难题退退退!

大家好,我是专注于交付的王小锤,看过交付铁三角系列故事(专注交付的我小锤,开发老哥铁子和售前大佬强哥)的同学想必都知道,我们三人服务于一家提供大数据分析服务的 ISV 企业,在应用交付过程中时…

小迈科技 X Hologres:高可用的百亿级广告实时数仓建设

通过本文,我们将会介绍小迈科技如何通过Hologres搭建高可用的实时数仓。 一、业务介绍 小迈科技成立于 2015 年 1 月,是一家致力以数字化领先为优势,实现业务高质量自增长的移动互联网科技公司。始终坚持以用户价值为中心,以数据…

移动云探索自主技术核心地带,拥抱开发者共筑全新技术生态

在数字经济向更多新领域渗透,5G、物联网等数字经济核心技术应用加速的助推下,云计算作为最主流的算力解决方案,需求增长迅速。随着国内云计算技术生态发展的不断成熟,各行各业上云的步伐也在不断加快。同时,由于企业的…

深度解读 RocketMQ 存储机制

RocketMQ 实现了灵活的多分区和多副本机制,有效的避免了集群内单点故障对于整体服务可用性的影响。存储机制和高可用策略是 RocketMQ 稳定性的核心,社区上关于 RocketMQ 目前存储实现的分析与讨论一直是一个热议的话题。本文想从一个不一样的视角&#x…

IOC-golang 的 AOP 原理与应用

AOP 与 IOC 的关系 AOP (面向切面编程)是一种编程设计思想,旨在通过拦截业务过程的切面,实现特定模块化的能力,降低业务逻辑之间的耦合度。这一思路在众多知名项目中都有实践。例如 Spring 的切点 PointCut 、 gRPC的…

达摩院技术创新全景|懂你的语音AI

过去十年,语音AI从实验室走向应用,语音搜索、交互早已融入日常。本文将带你一览达摩院语音AI技术创新全景,一起感受能听、会说、懂你的语音AI。 当你在家中与智能音箱进行交互对话,当你使用天猫超市或菜鸟裹裹,接到机器…

闲鱼对 Flutter-Native 混合工程解耦的探索

1. 闲鱼Flutter现状 闲鱼是第一个使用Flutter混合开发的大型应用,但闲鱼客户端开发最深入体会的痛点就是编译时长影响开发体验。在FlutterNative这种开发模式下,Native编译速度慢,模块开发无法突破。闲鱼集成了集团众多中间件,很…

算法通关第二十关-青铜挑战认识图结构

大家好我是苏麟 , 今天来聊聊图结构 . 我们平时在工作、学习中会大量使用图结构,不过呢在使用代码进行具体实现的时候极少使用图,主要是图里容易产生环,难以处理。 在算法里,考察图也不是很多,主要是图的表示非常复杂&…

我的前端成长之路:中医药大学毕业的业务女前端修炼之路

大家好,我是风月,2014年二进宫进入阿里,目前是业务平台体验技术数据服务前端团队负责人,负责 BizCharts 横向建设以及财鲸数据业务支撑。本次分享我将回顾作为业务前端从前端工程转型到数据可视化过程中的心路历程。 前端工程师的…

过去5年,PolarDB云原生数据库是如何进行性能优化的?

云数据库实现计算存储分离,支持计算与存储的独立扩展,其用户还可以享受按量付费等特性。这使得基于云数据库的系统更加高效、灵活。因此,构建并使用云原生数据库的势头愈演愈烈。另一方面,云化存储服务已经是云的标准能力&#xf…

ChatGPT 玩「脱」了,写了份毁灭人类计划书,还遭到了 Stack Overflow 的封杀.........

【CSDN 编者按】OpenAI 的新通用聊天机器人原型 ChatGPT 可谓是风靡一时,但却突遭 StackOverflow 封禁。整理 | 刘春霖 责编 | 张红月出品 | CSDN(ID:CSDNnews)在上周发布的《挑战 Google 搜索?OpenAI 发布最强 …

基于任务调度的企业级分布式批处理方案

背景 先来谈下什么是分布式批处理,从字面来理解就是有大批量的业务数据需要应用程序去批量计算处理,而通过单机模式去执行会耗费很长的处理时间,也不能充分发挥业务集群中每个应用节点处理能力。通过一些常见的分布式批处理方案,…

如何用一个插件解决 Serverless 灰度发布难题?

导读本文适合:想了解 Serverless 灰度发布的同学。认为当前 Serverless 灰度发布配置太复杂,寻求简洁版灰度发布流程的同学。想了解 Serverless Devs 组件和插件之间关系的同学。Serverless 灰度发布 什么是 Serverless ? Serverless 顾名思义就是无服…

阿里云云原生一体化数仓 — 分析服务一体化新能力解读

分析服务一体化一直都是阿里云离线实时一体化数仓的重要能力创新 分析服务一体化需求分析 业务在线化、运营精细化驱动数据实时化 随着互联网的信息,业务对于在线化、运营精细化的需求日益强烈,领导驾驶舱、实时大屏等,起到了越来越重要的…

一位 sealer maintainer 的心路历程

引言 在 2021 年四月左右,我有幸在 sealer 启动初期了解到其相关工作,并且不久后就作为初始的几位开发同学之一,加入到了 sealer 的开发工作中。 本文,我将回顾个人参与 sealer 开源项目的机缘巧合,参与过程中的挑战…

龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍

引言 BPF 是一个新的动态跟踪技术,目前这项技术正在深刻的影响着我们的生产和生活。BPF 在四大应用场景发挥着巨大作用: 系统故障诊断:它可以动态插桩透视内核。网络性能优化:它可以对接收和发送的网络包做修改和转发。系统安全…

线上故障突突突?如何紧急诊断、排查与恢复

概述 稳定性大于一切,因此我们需要有更有效的方式避免线上故障。在发生故障不可避免的假设下,我们需要能够快速修复,减少线上影响。基于以上这些想法,我们提出了 1-5-10 的快恢目标,所谓 1-5-10 的目标就是是要我们对…

巧用 API 网关构建大型应用体系架构

近期阿里云重磅发布了BizWorks一体化的云原生应用的开发和运营平台,内置阿里巴巴业务中台构建的最佳技术实践。BizWorks提供的产品能力,普遍适用于企业云原生应用高效开发以及企业业务能力沉淀和复用的场景。BizWorks提供业务架构师一整套的可视化业务建…

可观测|时序数据降采样在 Prometheus 实践复盘

基于 Prometheus 的监控实践中,尤其是在规模较大时,时序数据的存储与查询是其中非常关键,而且问题点较多的一环。如何应对大数据量下的长周期查询,原生的 Prometheus 体系并未能给出一个令人满意的答案。对此,ARMS Pro…