C+開發者怒了:這個無用的模塊設計最終會害死 C+!
2018 年年底,C 標準委員會歷史上規模最大的一次會議在美國 San Diego 召開,討論了哪些特性要加入到C 20中。其中,Modules便是可能進入 C 20 的一大重要特性:
「一直以來 C 一直通過引用頭文件方式使用庫,而其他90年代以後的語言比如 Java、C#、Go 等語言都是通過 import 包的方式來使用庫。現在 C 決定改變這種情況了,在 C 20 中將引入 Modules,它和 Java、Go 等語言的包的概念是類似的,直接通過 import 包來使用庫,再也看不到頭文件了。」
然而就是這一特性,前段時間在 Twitter 上引發了不小的討論。再加上諸多其他問題,「C 20 還未發布就已涼涼」的論調也早有苗頭。C 模塊化,究竟是問題多多的無用嘗試,還是如期待般能帶來其承諾的性能升級呢?
作者 |vector-of-bool
譯者 | 蘇本如
責編 |仲培藝
出品 |CSDN(ID:CSDNNews)
以下為譯文:
C Modules(模塊化)被視作 C 自誕生以來最大的變化,其設計有幾個基本目標:
1. 自頂向下隔離:模塊的「導入程序」不能影響正在導入的模塊的內容。導入源中編譯器(預處理器)的狀態與導入代碼的處理無關。
2. 自下而上隔離:模塊的內容不會影響導入代碼中預處理器的狀態。
3. 橫向隔離:如果兩個模塊由同一個文件導入,則它們之間不會「串擾」。導入語句的順序無關緊要。
4. 物理封裝:只有模塊顯式聲明為導出的實體才會對使用者可見。模塊中未導出的實體不會影響其他模塊中的名稱查找(除了 ADL可能有一些不同之處【依賴實參的名字查找】,但這就說來話長了)。
5. 模塊化介面:強制任何給定模塊的公共介面在稱為「模塊介面單元」(MIU)的單個 TU 中聲明。模塊介面子集的實現可以在稱為「分區」的不同 TU 中定義。
如果你期望Modules可以像 C 的許多其它功能一樣經久不衰,那麼你會注意到上面這個列表中缺少了「編譯速度」。然而,這是 C Modules 模塊最大的承諾之一。模塊帶來的速度提升可能就是歸功於上面的設計。
下面我列出從Modules設計中受益匪淺的 C 編譯的幾個方面,按照從最明顯到最不明顯的順序:
1. 標記化緩存(Tokenization Caching):由於 TU 的隔離,當模塊後面導入另一個 TU 時,可以緩存已經標記化的 TU。
2. 解析樹緩存(Parse-tree Caching):和標記化緩存一樣。標記化和解析是 C 編譯中開銷最大的操作之一。我自己的測試顯示,對於具有大量預處理輸出的文件,解析可能會佔用高達 30% 的編譯時間。
3. 延遲重編譯(Lazy Re-generation):如果foo導入了bar,然後我們修改了bar的實現,我們可以不需要對foo立即重新編譯。只有對bar介面修改後才需要重新編譯foo。
4. 模板專門化:這一點比較微妙,可能需要更多的工作來實現,但潛在的加速是巨大的。簡而言之,模塊介面單元中出現的類或函數模板在經過專門化處理後可以在磁碟上緩存並供後續需要時載入。
5. 內聯函數代碼複製緩存:內聯函數(包括函數模板和類模板的成員函數)的代碼複製結果可以緩存,然後由編譯器後端重新載入。
6. 內聯函數省略代碼複製:extern template允許編譯器省略對函數和類模板執行代碼複製,這對編輯器的代碼去重操作非常有益。模塊允許編譯器隱式執行更多的extern template-style優化。
看上去模塊設計相當不錯,不是嗎?
但是我們都忽略了一個非常可怕且極為糟糕的缺陷。
還記得…… Fortran 嗎?
FORTRAN 實現了與 C 的設計有點相似的模塊系統。幾個月前,SG15 工具研究小組在聖地亞哥提交了一篇文章(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1300r0.pdf),據我所知,這篇文章迄今為止沒有得到任何相關人士的討論和評論。
文章要點摘錄如下:
1. 我們有模塊foo和bar,分別由foo.cpp和bar.cpp定義。
2.bar.cpp里有import foo;語句。
3. 在編譯bar.cpp時,如何確保import foo被解析?當前的設計和實現有一個為foo定義的所謂「二進位模塊介面」(簡稱BMI)。這個 BMI 是文件系統中描述模塊foo導出介面的文件。我就叫它foo.bmi, 文件擴展名在這裡無所謂。
4.foo.bmi是編譯foo.cpp的副產品。編譯foo.cpp時,編譯器將生成foo.o和foo.bmi。因此,必須在bar.cpp之前編譯foo.cpp!
趁著警鈴還沒有拉響,我們來討論一下我們目前使用頭文件的工作方式:
1. 我們有一個模塊foo,由foo.cpp和foo.hpp定義; 和另一個模塊bar,由bar.cpp和bar.hpp定義。
2.bar.cpp中有#include 。
3. 在編譯bar.cpp時,如何確保#include被解析?這很簡單:確保foo.hpp存在於 header 搜索路徑列表的目錄中。我們不需要做任何額外的預處理。
4. 對模塊foo和bar的編譯沒有次序要求,可以並行處理。
並行化可能是提高 build 性能最重要的方面。優化 build 時,你無需再考慮並行化,因為它已經存在了。
模塊改變了這一點。模塊的導入導致了一個編譯時間的依賴項,這在#include語句中並沒有體現。(關於模塊編譯的次序問題,可參考:https://vector-of-bool.github.io/2018/12/20/build-like-ninja-1.html)。
Rene Rivera 最近在《Are modules fast?》(https://bfgroup.github.io/cpp_tooling_stats/modules/modules_perf_D1441R1.html)一文中探討了這種設計的後果。
劇透一下 Rene 文章的結論:答案是否定的,或者更準確一點來講,這很微妙,但大多數情況下答案仍然是不。這篇文章中使用的當前模塊實現是非常原始的,但仍然在了解哪些模塊看上去對性能有幫助這方面有一定的參考價值。可以期待,隨著硬體並行性的提升,header 的引導模塊變得越來越重要,而且與 DAG 深度(即互相導入的模塊鏈的長度)也有關係。隨著 DAG 深度的增加,模塊會越來越慢,而 header 則保持相當穩定,即使是對於接近 300 的「極端」深度。
一個徒勞的掃描任務
假設我有下面的源文件:
importgreetings;
importstd.iostream;
intmain(){
std::cout
}
這很簡單。因為我們導入了一些模塊,所以我們需要先編譯greetings和std.iostream,然後才能編譯這個文件。
那麼,讓我們來……
emmm……
怎麼啦?
我們只有一個包含兩個 import 的源文件,僅此而已,別無他物。我們不知道greetings是在哪裡定義的,我們需要找到這個包含module greetings;語句的文件。
在銀河系另一側的talk.cpp文件看起來很可能是:
module;
#ifdefFROMBULATE
#include
#endif
#ifndefABSYNTH
exportmodulesomething.pie;
#endif
importstd.string;
exportnamespacegreeting {
std::stringenglish();
}
它定義了我們想要的greeting::english函數。但是我們怎麼知道這是正確的文件呢?它並沒有module greetings;這一行!
但它某些時候確實是我們要的。當我們使用-DFROMBULATE編譯時,文件hello.h會被粘貼到源文件中。讓我們看看hello.h裡面有什麼?
#ifdef__SOME_BUILTIN_MACRO__
#defineMODULE_NAME greetings
#else// Legacy module name
#defineMODULE_NAME salutations
#endif
exportmoduleMODULE_NAME;
Oh no!
好吧好吧……別擔心。我們需要做的就是……運行預處理器來檢查文件中是否出現module salutations或module greetings。
這是可以的,但是有 4201 個文件可以定義可以被導入的模塊,其中任何一個都可能有module greetings;。
另外,我們還不能使用自己的預處理器實現,需要精確地運行編譯這段代碼的預處理器。看到__SOME_BUILTIN_MACRO__了嗎?我們不知道那是什麼。如果我們沒有正確地對它進行編譯,編譯就會失敗。更糟的是,我們甚至可能會錯誤地編譯此文件。
那麼我們能做什麼呢?我們可以在預處理完所有文件後緩存所有模塊的名稱,對嗎?那麼,我們在哪裡存儲這個映射表呢?當我們想用一個不同的編譯器編譯,生成不同的映射表時會發生什麼?如果我們添加需要掃描的新文件怎麼辦?為了檢查任何模塊是否添加、刪除或重命名了,我們是否需要在每次構建時搜索這些包含了數千個源文件的所有目錄?在那些啟動進程和/或訪問文件需要較大開銷的系統上,這些成本也將會疊加上去。
可能的解決方案
這兩個問題雖然不同,但卻是相關的,我(和許多其他人)認為模塊設計的一個改變可以解決這兩個問題, 那就是模塊介面單元的位置必須是確定的。
有兩種備選方案可以實施:
1. 強制從模塊名稱派生 MIU 文件名。這模擬了頭文件名的設計,它與如何從#include指令中找到頭文件名直接相關。
2. 提供一個「manifest」或「mapping」文件,描述基於模塊名的 MIU 文件路徑。此文件需要用戶提供,否則我們將同樣遇到上文描述的掃描問題。
有了確定且易於定義的 MIU lookup(查詢),我們就可以進入下一個必要步驟:必須延遲生成模塊的 BMI。
TU 之間的編譯順序將扼殺 module adoption 的進程。即使是相對較淺的 DAG 深度也比與頭文件相同的深度慢得多。唯一的答案是 TU 編譯必須是可並行的,即使是導入其他 TU 時。
在這方面,C 最好模仿 Python 的導入實現:當遇到新的導入語句時,Python 將首先找到對應於該模塊的源文件,然後以確定性的方式查找預編譯的版本。如果預編譯版本已經存在並且是最新的,就使用它;如果不存在預編譯版本,則將編譯源文件,並將生成的位元組碼寫入磁碟。然後載入此位元組碼。如果兩個解釋器實例同時遇到同一個未編譯的源文件,它們將競爭寫位元組碼。不過,競爭並不重要,它們都會得出相同的結論,並將相同的文件寫入磁碟。
為了方便 DAG 中 TU 的並行編譯,C 模塊必須以相同的方式實現。提前編譯 BMI 是不可能的。相反,當編譯器第一次遇到有關模塊的 import 語句時,應該延時生成 BMI。Build 系統根本不應該與 BMI 有關。
只有當一個 MIU 的位置對於編譯器是確定的時候,以上這些才能實現。
前景渺茫
前段時間,Twitter 上發生的事讓人心煩意亂。Kona 會議前的郵件列表在 1 月 25 日開放了。在發布的許多文章中,有一篇《關注模塊的工具能力(Concerns about module toolability)》(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1427r0.pdf),其作者和貢獻者名單中很多是來自業界的系統和工具構建工程師。我想呼籲權威人士的關注,但我覺得這份名單中的人才是最有資格提供 module toolability 反饋的人。
這篇文章的誕生源於許多工具作者和合作者(並不局限於論文中所提及的,包括我自己)的關注,因為大家都深深感到自己長久以來對於模塊的關注都被忽視了。
SG15 之外的人一直熱衷於反駁關於 module toolability 問題的討論,他們聲稱 SG15 缺乏必要的實現經驗,無法對模塊這個話題提出有用的建議。
SG15 只搞過面對面的會議,上次在聖地亞哥的會議也沒起到什麼作用,因為主席不在,而且大家急急忙忙參會,沒時間進行任何有用的討論。由於在官方的 WG21 會議之外沒有安排 SG15 會議,因此其成員很難保證更新並協同工作。此外,SG15 曾多次嘗試重提已經被拒絕的問題,被拒絕的原因是因為他們提出的問題被認為「超出了 C 語言範圍」。
關於 Kona 會議前郵件列表的推文催生了關於 C 模塊化的討論:關於 module toolability,該相信誰?(https://twitter.com/horenmar_ctu/status/1089542882783084549)。
這場討論最終以要求 SG15 「他媽的閉嘴」而告終,除非 SG15 能夠提供代碼示例來證明它們所提到的問題。但是這個示例代碼,無法在當前的任何編譯器中實現,也不能在任何當前的構建系統中實現。所以即使這些問題確實存在,這個要求也只能得出一個否定的結論,因為這是一個無法憑經驗完成的任務。也就是說,要求 SG15 提供代碼根本是一個無法永遠完成的任務。
這些問題沒有繼續討論下去,也沒有被推翻。甚至沒有人再提到《關注模塊的工具能力》中列出的問題。我們只是被簡單地告知要相信一些大人物比我們更了解 C 模塊(這裡我要再次呼籲權威人士介入)。
支持目前模塊設計的人尚未證明模塊能適應大規模生產環境,但是他們卻要求 SG15 提供模塊不能滿足大規模生產的證據。儘管已有的模塊部署並沒有使用當前的設計,也沒有使用真實環境中構建實際系統所需的自動模塊掃描。
如果模塊被合併,結果發現它們不能以良好的性能和靈活的方式實現,那麼人們就不會使用模塊。如果一個 broken module 建議被合併到 C 中,後果可能是不可彌補恢復的,C 也將永遠得不到模塊設計承諾帶來的好處。
至於針對當前模塊設計的改進方案能成功解決這些問題呢?我不能給出確定的答案,但我和許多人都認為 C Modules 有重大問題需要解決。
然而,從其他人的做法來看,SG15 怎麼想似乎並不重要,他們的提議總是被缺乏 C 工具經驗的人否決, 他們在整個討論中沒有任何發言權,提出的任何問題都被認定為「未經證實」和「超出範圍」而不予考慮。
我不太敢指責這種行為的後果,我也並不熱衷「人際衝突」。然而,我更擔心 C 這個無用的模塊設計最終會害死自己。
原文:https://vector-of-bool.github.io/2019/01/27/modules-doa.html
本文為 CSDN 翻譯,如需轉載,請註明來源出處。作者獨立觀點,不代表 CSDN 立場。
【完】
從熱點項目到生態現狀
開源遲暮 or 開源大勢?
關於開源,你有什麼想說的?
CSDN 開源徵稿啦!
歡迎投稿分享你的獨特見解,
開源路上我們攜手同行!
熱 文推 薦
TAG:CSDN |