我们如何将 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 的使用替换为相应编译器的库(例如 Visual Studio 2015)。 Windows,gcc 7 用于 Linux,clang 8 用于 macOS).

幸运的是,我们的代码大部分是按照规范编写的,没有使用任何花哨的技巧,因此迁移到新库的过程相对顺利。我们使用脚本替换了源文件中的类型、类、命名空间和包含文件的名称。此次迁移影响了 10,000 个源文件(总共 14,000 个)。wchar_t 被替换为 char16_t;我们决定放弃使用 wchar_t,因为 char16_t 在所有操作系统上都只占用 2 个字节,并且不会影响代码兼容性。 Windows и Linux.

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

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

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

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

大型项目进行大规模变更后经常会出现这种情况,源代码的第一个迭代版本也并非一帆风顺,尤其是在调试迭代器支持方面。 Windows-实施。我们一步一步地推进,到 2017 年春季(1C:Enterprise 版本 8.3.11),迁移完成。

结果

过渡到 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

为具有 DDoS 保护、VPS VDS 服务器的站点购买可靠的主机 🔥 购买具备 DDoS 防护的可靠网站托管服务,包括 VPS 和 VDS 服务器 | ProHoster