GCC使用特定於平臺的技巧來完全避免原子操作在快速路徑上,利用它可以比call_once或雙重檢查更好地分析static
的事實。
由於複覈使用原子作爲其避免種族情況的方法,因此每次都必須支付收購的價格。這不是一個高價,但它是一個價格。
它必須支付這個費用,因爲原子在所有情況下都必須保持原子,甚至像比較交換這樣的困難操作。這使得優化非常困難。一般來說,編譯器必須將其保留在中,以防您使用該變量不僅僅是雙鎖。它沒有簡單的方法來證明你從不使用原子上更復雜的操作之一。
另一方面,static
是高度專業化和語言的一部分。它的設計從一開始就很容易初始化。因此,編譯器可以使用更通用版本不可用的快捷方式。 The compiler actually emits爲靜態下面的代碼:
一個簡單的函數:
void foo() {
static X x;
}
被改寫內部海灣合作委員會:
void foo() {
static X x;
static guard x_is_initialized;
if (__cxa_guard_acquire(x_is_initialized)) {
X::X();
x_is_initialized = true;
__cxa_guard_release(x_is_initialized);
}
}
這看起來很像一個雙重檢查鎖。但是,編譯器會在這裏作弊。它知道用戶不能直接使用cxa_guard
。它知道它只用於編譯器選擇使用它的特殊情況。因此,有了這些額外的信息,它可以節省一些時間。 CXA防護規範如同分佈式一樣共享一個common rule:__cxa_guard_acquire
決不會修改防護的第一個字節,並且__cxa_guard__release
會將其設置爲非零。
這意味着每個守衛都必須是單調的,並且它明確指出哪些操作會這樣做。因此它可以利用主機平臺內現有的賽道保護。例如,在x86上,由強烈同步的CPU保證的LL/SS保護足以實現這種獲取/釋放模式,因此它可以在執行雙鎖時執行第一個字節的原始讀取,而不是獲取閱讀。這是唯一可能的,因爲GCC不使用C++原子API來執行雙重鎖定 - 它使用platform specific approach。
在一般情況下,GCC無法優化原子。在被設計爲不太同步的體系結構(例如那些爲1024+核心設計的體系結構)上,GCC不會依賴架構來爲它做LL/SS。因此GCC被迫實際發射原子。但是,在x86和x64等常見平臺上,速度可能更快。
call_once
可以具有GCC靜態效率,因爲它類似地限制了可以對once_flag
執行的操作次數,以使其可以應用於原子的一小部分功能。權衡是靜態使用更爲方便,只要適用,但call_once
適用於靜態不足的很多情況(例如由動態生成的對象擁有once_flag
)。
在這些更高平臺上,靜態和call_once
之間的性能稍有差異。許多這些平臺雖然不提供LL/SS,但至少可以提供非整數的非撕裂讀取。這些平臺可以使用這個和線程特定的指針來做per-thread epoch counting to avoid atomics。這對於靜態或call_once
已足夠,但取決於計數器未翻轉。如果你沒有撕掉64位整數,call_once
不得不擔心翻轉。實施可能會也可能不會擔心這一點。如果忽略這個問題,它可以像靜態一樣快。如果它注意到這個問題,它必須像原子一樣慢。 Static在編譯時知道有多少個靜態變量/塊,所以它可以證明在編譯時沒有翻轉(或者至少可以自信!)
預先警告說VC++在線程安全函數local靜。他們不在VS2013中。但據報道是在VS2014:http://blogs.msdn.com/b/vcblog/archive/2014/06/11/c-11-14-feature-tables-for-visual-studio-14-ctp1。 aspx – 2014-09-24 15:07:08
另一方面,GCC可以使本地靜態數據比call_once更快或者雙重檢查,因爲它可以使用平臺特定的技巧來避免任何原子操作。 – 2014-11-29 07:28:29
@CortAmmon如果您將該帖子作爲回答並附帶一些證據,我會接受。 – Praxeolitic 2014-11-29 08:00:23