簡體   English   中英

奇怪的C ++模式減少了編譯時間

[英]Strange C++ pattern to reduce compilation time

我在Tizen Project的OpenSource代碼中找到了可以縮短項目編譯時間的模式。 它在項目的許多地方使用。

作為一個例子,我選擇了一個類名ClientSubmoduleSupport 它很短。 以下是它們的來源: client_submode_support.hclient_submode_support.cpp

正如您在client_submode_support.h中看到的那樣,它定義了一個ClientSubmoduleSupportclient_submode_support.cpp ,它定義了ClientSubmoduleSupportImplementation類,它為ClientSubmoduleSupport執行任務。

你知道這種模式嗎? 我很好奇這種方法的優點和缺點。

這種模式被稱為“ ”,也被稱為“ Pimpl成語 ”。

意圖: “將抽象與其實現分離,以便兩者可以獨立變化”

Souce:“四人幫”設計模式書

正如sergej已經提到的那樣,它是Pimpl成語,它也是Bridge設計模式的一個子集。

但我想為這個話題提供一個C視角。 我感到很驚訝,因為我對C ++有了更多的知名度,因為類似的做法在C中應用了類似的優點和缺點(但由於C中缺少一些額外的專業人員)。

C透視

在C中,通常的做法是使用一個指向前向聲明struct的不透明指針,如下所示:

// Foo.h:
#ifndef FOO_H
#define FOO_H

struct Foo* foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo);

#endif

// Foo.c:
#include "Foo.h"

struct Foo 
{
    // ...
};

struct Foo* foo_create(void)
{
    return malloc(sizeof(struct Foo));
}

void foo_destroy(struct Foo* foo)
{
    free(foo);
}

void foo_do_something(struct Foo* foo)
{
    // Do something with foo's state.
}

這對於Pimpl還有一個類似的優點/缺點,還有一個C的附加專業版。在C中,沒有structs private說明符,這使得它成為隱藏信息和防止從外部世界訪問struct內部的唯一方法。 因此它成為隱藏和阻止訪問內部的手段。

在C ++中,有一個很好的private說明符允許我們阻止對內部的訪問,但我們不能完全隱藏它們與外部世界的可見性,除非我們使用類似Pimpl東西,它基本上包含了這種不透明指針的C形思想-declared UDT中的形式class與一種或多種構造器和析構函數。

效率

也許獨立於獨特上下文的最明顯的缺點之一是這種表示將可能是單個連續內存塊的內容分成兩個塊,一個用於指針,另一個用於數據字段,如下所示:

[Opaque Pointer]-------------------------->[Internal Data Fields]

...這通常被描述為引入了一個額外的間接層,但這不是間接性,這是性能問題,而是對引用局部性的降級,以及堆分配時的額外強制緩存未命中和頁面錯誤第一次訪問這些內部。

通過這種表示,我們也不能簡單地在堆棧上分配我們需要的所有東西。 只能將指針分配給堆棧,而內部必須在堆上分配。

如果我們存儲一堆這些句柄的數組(在C中,不透明指針本身,在C ++中,一個包含一個的對象),與此相關的性能成本往往是最明顯的。 在這種情況下,我們最終會得到一個數百萬個指針,這些指針可能指向所有地方,我們最終會以增加的頁面錯誤和緩存未命中以及堆(免費存儲)的形式支付費用。分配/解除分配開銷。

這最終會讓我們留下類似於Java的性能,存儲一百萬個用戶定義類型實例的通用列表並按順序處理它們(運行和隱藏)。

效率:固定分配器

顯着減輕(但不消除)此成本的一種方法是使用例如O(1)固定分配器,其為內部構件提供更連續的存儲器布局。 在我們使用Foos數組的情況下,這可以有很大幫助,例如,通過使用分配器,允許Foo內部存儲(更多)連續的內存布局(改善引用的局部性)。

效率:批量接口

采用一種非常不同的設計思維方式的方法是開始在較粗糙的層面上對公共接口進行建模,使其成為Foo聚合( Foo實例容器的接口),並隱藏甚至從外部世界單獨實例化Foo的能力。 這僅適用於某些情況,但在這種情況下,我們可以將成本降低到整個容器的單個指針間接,如果公共接口由在許多隱藏Foo上運行的高級算法組成,則開始變得幾乎免費對象一下子。

作為一個明顯的例子(雖然希望沒有人這樣做),我們不想使用Pimpl策略來隱藏圖像的單個像素的細節。 相反,我們希望在整個圖像級別對我們的界面進行建模,該圖像級別由一堆像素和適用於一堆像素的公共操作組成。 與單個粒子與粒子系統相同的想法,甚至可能是視頻游戲中的單個精靈。 如果我們發現自己處於性能熱點,我們總是可以增加我們的接口,因為它們過於精細化了一些事情,並且為它支付了內存或抽象懲罰(動態調度)。

“如果你想要達到最佳性能,那么你必須得到充分的幫助!完成這些接口!趕快去看看!” - 用螺絲刀穿過某個人的頸靜脈之后想象中Arnie的建議。

打火機標頭

可以看出,這些實踐完全隱藏了來自外部世界的classstruct的內部。 從編譯時和標題的角度來看,這也可以作為一種解耦機制。

Foo的內部通過頭文件不再對外界可見時,構建時間會立即下降,只是因為具有較小的頭。 也許更重要的是, Foo的內部可能需要包含其他頭文件,如Bar.h 通過隱藏內部,我們不再需要Foo.h來包含Bar.h (只有Foo.cpp會包含它)。 由於Bar.h可能還包含具有級聯效果的其他標頭,因此可以大大減少預處理器所需的工作量,並使我們的頭文件比使用Pimpl之前更加輕量級。

因此,雖然Pimpls有一些運行時成本,但它們可以減少構建時間成本。 即使是性能最關鍵的領域,大多數復雜的代碼庫也會比最高的運行時效率更有利於生產力。 從生產力的角度來看,冗長的構建時間可能是殺手鐧,因此在運行時交易稍微性能降低的構建性能可能是一個很好的權衡。

級聯變化

此外,通過隱藏Foo內部的可見性,對其進行的更改不再影響其頭文件。 這允許我們現在簡單地改變Foo.cpp ,例如,改變Foo的內部,只有這一個源文件需要在這種情況下重新編譯。 這也與構建時間有關,但特別是在小的(可能非常小的)變化的背景下,必須重新編譯各種事物可能是真正的PITA。

作為獎勵,如果他們不需要重新編譯所有內容以對某些類的私人細節進行一些小改動,這也可以提高團隊中所有隊友的理智。

有了這個,每個人都可以更快地完成他們的工作,在他們的日程安排中留出更多時間來訪問他們最喜歡的酒吧並受到打擊等等。

API和ABI

一個不那么明顯的專業人士(但在API上下文中非常重要)是當您為插件開發人員(包括第三方編寫控制之外的源代碼)公開API時,例如,在這種情況下,如果您公開內部狀態classstruct的方式使得插件訪問的句柄直接包含這些內部,我們最終得到一個非常脆弱的ABI。 二進制依賴可能開始類似於這種性質:

[Plugin Developer]----------------->[Internal Data Fields]

這里最大的問題之一是,如果對這些內部狀態進行任何更改,內部的ABI會破壞哪些插件直接依賴於工作。 實際結果:現在我們最終得到了一堆插件二進制文件,可能是各種各樣的人為我們的產品編寫的,直到為新的ABI發布新版本之后它們才能工作。

這里一個不透明的指針(包括Pimpl )引入了一個中介,保護我們免受這種ABI破壞。

[Plugin Developer]----->[Opaque Pointer]----->[Internal Data Fields]

...當你現在可以自由更改私人內部而不會冒這樣的插件破壞風險時,這可以在向后插件兼容性方面走很長的路。

優點和缺點

以下是一些優點和缺點的摘要以及一些其他的小問題:

優點:

  • 輕量級標頭的結果。
  • 減少級聯構建更改。 可以在僅影響一個編譯單元(也就是翻譯單元,即源文件)而不是許多編譯單元的情況下更改內部。
  • 隱藏內部結構,即使從美學/文檔的角度來看也是有益的(不要使用公共界面顯示客戶端,而不是為了使用它而需要查看的客戶端)。
  • 防止客戶端依賴脆弱的ABI,這會破壞修改單個內部細節的時刻,從而減少由於ABI更改而導致的二進制文件的級聯破壞。

缺點:

  • 運行時效率(由較大的接口或高效的固定分配器緩解)。
  • 次要:為實現者讀取/寫入的更多樣板代碼(盡管沒有重復任何非平凡的邏輯)。
  • 無法應用於需要在生成代碼的站點上顯示其完整定義的類模板。

TL; DR

所以無論如何,上面是對這個成語的簡要介紹,以及在C之前的實踐的一些歷史和相似之處。

您將使用此模式主要是在為第三方開發人員使用的庫編寫代碼時,您無法更改API。 它使您可以自由地更改函數的底層實現,而無需客戶在使用新版本的庫時重新編譯代碼。

(我已經看到API穩定性要求被寫入法律合同)

在J.Lakos中廣泛討論了使用這種模式來減少編譯時間。 “大規模C ++軟件設計”(Addison-Wesley,1996)。

還有這種方法的優點一些討論通過香草薩特這里

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM