我们如何将 10 万行 C++ 代码转换为 C++14 标准(然后转换为 C++17)

前段时间(2016年秋天),在开发下一版本的1C:Enterprise技术平台时,开发团队内部出现了关于支持新标准的问题 C ++ 14 在我们的代码中。 正如我们所假设的,向新标准的过渡将使我们能够更优雅、简单和可靠地编写许多东西,并将简化代码的支持和维护。 如果不是考虑到代码库的规模和代码的具体功能,翻译似乎并没有什么特别之处。

对于那些不知道的人来说,1C:Enterprise 是一个用于快速开发跨平台业务应用程序及其在不同操作系统和 DBMS 上执行的运行时的环境。 一般来说,该产品包含:

  • 应用服务器集群,在 Windows 和 Linux 上运行
  • 客户,通过 http(s) 或其自己的二进制协议与服务器一起工作,适用于 Windows、Linux、macOS
  • 网页客户端,在 Chrome、Internet Explorer、Microsoft Edge、Firefox、Safari 浏览器中运行(用 JavaScript 编写)
  • 开发环境(配置器),适用于 Windows、Linux、macOS
  • 管理工具 应用程序服务器,在 Windows、Linux、macOS 上运行
  • 手机客户端,通过http(s)连接到服务器,适用于运行Android、iOS、Windows的移动设备
  • 移动平台 — 一个用于创建离线移动应用程序的框架,具有同步能力,在 Android、iOS、Windows 上运行
  • 开发环境 1C:企业开发工具,用Java编写
  • 服务器 交互系统

我们尝试尽可能为不同的操作系统编写相同的代码——服务器代码库是99%通用的,客户端代码库是大约95%。 1C:Enterprise技术平台主要用C++编写,大致的代码特征如下:

  • 10万行C++代码,
  • 14个文件,
  • 60万个班级,
  • 五十万种方法。

所有这些东西都必须翻译成 C++14。 今天我们将告诉您我们是如何做到这一点以及我们在过程中遇到了什么。

我们如何将 10 万行 C++ 代码转换为 C++14 标准(然后转换为 C++17)

免责声明

下面写的所有关于慢/快工作、(而不是)各种库中标准类的实现所消耗的大量内存都意味着一件事:这对我们来说是正确的。 标准实现很可能最适合您的任务。 我们从自己的任务开始:我们获取客户的典型数据,对其运行典型场景,查看性能、消耗的内存量等,并分析我们和客户对这些结果是否满意。 他们的行动取决于。

我们拥有什么

最初,我们使用 Microsoft Visual Studio 编写 1C:Enterprise 8 平台的代码。 该项目始于 2000 年代初,我们有一个仅限 Windows 的版本。 自然,此后代码一直在积极开发,许多机制已被完全重写。 但代码是按照1998年的标准编写的,例如我们的右尖括号之间用空格分隔,这样编译就能成功,如下所示:

vector<vector<int> > IntV;

2006年,随着平台版本8.1的发布,我们开始支持Linux并切换到第三方标准库 端口。 转变的原因之一是使用宽线。 在我们的代码中,我们始终使用基于 wchar_t 类型的 std::wstring。 它在 Windows 中的大小为 2 字节,在 Linux 中默认为 4 字节。 这导致客户端和服务器之间的二进制协议以及各种持久数据不兼容。 使用 gcc 选项,您可以在编译期间指定 wchar_t 的大小也是 2 个字节,但是您可以忘记使用编译器中的标准库,因为它使用 glibc,而 glibc 又被编译为 4 字节 wchar_t。 其他原因是标准类的更好实现、对哈希表的支持,甚至模拟我们积极使用的在容器内移动的语义。 正如他们最后但并非最不重要的那样,还有一个原因是弦乐性能。 我们有自己的字符串类,因为...... 由于我们软件的特殊性,字符串操作被广泛使用,这对我们来说至关重要。

我们的字符串基于 2000 年代初期表达的字符串优化思想 安德烈·亚历山德雷斯库。 后来,当 Alexandrescu 在 Facebook 工作时,根据他的建议,Facebook 引擎中使用了一条遵循类似原理的语句(参见库 蠢事).

我们的生产线使用了两种主要的优化技术:

  1. 对于短值,使用字符串对象本身的内部缓冲区(不需要额外的内存分配)。
  2. 对于所有其他的,都使用力学 写时复制。 字符串值存储在一处,并且在赋值/修改期间使用引用计数器。

为了加快平台编译速度,我们从 STLPort 变体(我们没有使用)中排除了流实现,这使我们的编译速度提高了约 20%。 随后我们不得不有限地使用 提升。 Boost 大量使用流,特别是在其服务 API 中(例如,用于日志记录),因此我们必须对其进行修改以删除流的使用。 这反过来又让我们很难迁移到新版本的 Boost。

第三条道路

当转向 C++14 标准时,我们考虑了以下选项:

  1. 将我们修改的STLPort升级到C++14标准。 这个选择非常困难,因为... 对 STLPort 的支持已于 2010 年停止,我们必须自己构建所有代码。
  2. 转换到与 C++14 兼容的另一个 STL 实现。 非常希望此实现适用于 Windows 和 Linux。
  3. 针对每个操作系统进行编译时,请使用相应编译器内置的库。

由于工作量太大,第一个选项被彻底拒绝。

我们考虑了第二种选择一段时间; 被视为候选人 库++,不过当时Windows下还不行。 要将 libc++ 移植到 Windows,您必须做很多工作 - 例如,自己编写与线程、线程同步和原子性有关的所有内容,因为 libc++ 用于这些领域 POSIX API.

而我们选择了第三条路。

过渡

因此,我们必须将 STLPort 的使用替换为相应编译器的库(适用于 Windows 的 Visual Studio 2015、适用于 Linux 的 gcc 7、适用于 macOS 的 clang 8)。

幸运的是,我们的代码主要是根据指南编写的,没有使用各种巧妙的技巧,因此在脚本的帮助下,迁移到新库的过程相对顺利,这些脚本替换了源代码中的类型、类、命名空间和包含的名称文件。 迁移影响了 10 个源文件(总共 000 个)。 wchar_t 被 char14_t 取代; 我们决定放弃使用 wchar_t,因为char000_t 在所有操作系统上都占用 16 个字节,并且不会破坏 Windows 和 Linux 之间的代码兼容性。

有一些小冒险。 例如,在 STLPort 中,迭代器可以隐式转换为指向元素的指针,并且在我们的代码中的某些地方使用了这一点。 在新的图书馆中,不再可能这样做,必须手动分析和重写这些段落。

这样,代码迁移就完成了,代码是针对所有操作系统编译的。 是时候进行测试了。

转换后的测试显示,与旧版本的代码相比,性能下降(在某些地方高达 20-30%),内存消耗增加(高达 10-15%)。 这尤其是由于标准字符串的性能不佳所致。 因此,我们再次不得不使用我们自己的、稍作修改的生产线。

还揭示了嵌入式库中容器实现的一个有趣功能:内置库中的空(无元素) std::map 和 std::set 分配内存。 并且由于实现特性,在代码中的某些地方创建了相当多的这种类型的空容器。 标准内存容器为一个根元素分配了一点,但对我们来说这至关重要 - 在许多场景中,我们的性能显着下降,内存消耗增加(与 STLPort 相比)。 因此,在我们的代码中,我们将内置库中的这两类容器替换为 Boost 中的实现,而这些容器没有此功能,这解决了速度变慢和内存消耗增加的问题。

正如在大型项目中进行大规模更改后经常发生的那样,源代码的第一次迭代并不是没有问题的,特别是在这里,Windows 实现中对调试迭代器的支持派上了用场。 我们一步步向前推进,到2017年春天(版本8.3.11 1C:Enterprise)迁移完成。

结果

过渡到 C++14 标准花了大约 6 个月的时间。 大多数时候,一名(但非常合格的)开发人员负责该项目,并在最后阶段负责特定领域的团队代表加入 - UI、服务器集群、开发和管理工具等。

这一转变极大地简化了我们迁移到最新版本标准的工作。 因此,版本1C:Enterprise 8.3.14(正在开发中,计划于明年初发布)已经转移到标准 C++17.

迁移后,开发人员有更多选择。 如果早些时候我们有自己的 STL 修改版本和一个 std 命名空间,那么现在我们在 std 命名空间、stdx 命名空间中拥有来自内置编译器库的标准类 - 我们的线路和容器针对我们的任务进行了优化,在 boost 中 -最新版本的提升。 开发人员使用那些最适合解决他的问题的类。

移动构造函数的“本机”实现也有助于开发(移动构造函数)对于许多类。 如果一个类具有移动构造函数,并且该类被放置在容器中,则 STL 会优化容器内元素的复制(例如,当容器扩展并且需要更改容量并重新分配内存时)。

美中不足

也许迁移最令人不快(但不是关键)的后果是我们面临着数量的增加 目标文件,包含所有中间文件的完整构建结果开始占用 60-70 GB。 这种行为是由于现代标准库的特殊性造成的,现代标准库对生成的服务文件的大小不再那么重要。 这并不影响编译后的应用程序的运行,但确实给开发带来了许多不便,特别是增加了编译时间。 构建服务器和开发人员计算机上对可用磁盘空间的要求也在不断增加。 我们的开发人员并行处理平台的多个版本,数百 GB 的中间文件有时会给他们的工作带来困难。 这个问题令人不快,但并不严重;我们暂时推迟了它的解决方案。 我们正在考虑将技术作为解决该问题的选择之一 统一构建 (特别是Google在开发Chrome浏览器时使用它)。

来源: habr.com

添加评论