我們如何將 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並切換到第三方標準庫 STL埠。 轉變的原因之一是使用寬線。 在我們的程式碼中,我們總是使用基於 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瀏覽器時使用它)。

來源: www.habr.com

添加評論