yebangyu部落格 詭異的程式效能問題

2021-10-25 16:06:08 字數 4409 閱讀 2405

**:

本文所使用的環境是ubuntu 14.04 32bit系統,intel i5處理器,x86體系結構

如果我說下面的程式存在效能問題,您信嗎?

123

4567

891011

1213

1415

1617

1819

2021

22

#includeint32_t global[2]=;

void f()

}void g()

}int main()

這個程式,在我的電腦上,執行時間為:

real	0m0.822s

user 0m1.596s

sys 0m0.000s

有人說,兩個執行緒分別操作不同的計數器,這麼完美的程式,會有效能問題?

答案是:有。

恩,原因在於大名鼎鼎的false sharing。如果您看過我以前寫的這篇部落格,應該還記得:現在的計算機一般由乙個記憶體、乙個cpu組成,而包含多個cpu corescache。如這幅圖所示:

cachelinecache塊單位,乙個cacheline大小一般在32256位元組左右。cacheline是這張圖中不同模組的資料互動元素。

在上面程式中,global是兩個4位元組變數構成的陣列,大小為8位元組,很可能被放到同乙個cacheline裡。當執行在cpu1 core上的執行緒thread1修改了global[0]時,會讓執行在cpu2 core上對應global[0]global[1]cacheline失效,因此執行在cpu2 core上的執行緒thread2修改global[1]時會發生cache miss。在它修改global[1]後,就讓cpu1 core中的cacheline失效。

很明顯,這裡面由於cacheline ping pong帶來大量為了快取一致性而產生的開銷。

因此這種false sharing發生在多核、多執行緒環境中。單核或者單執行緒不會有false sharing問題。

遺憾的是,程式裡存在這樣的問題,並不容易通過肉眼發現。

幸運的是,這種問題一旦知道,就比較好解決。

解決方法一:讓這兩個計數器間隔足夠大,讓它們不要在同乙個cacheline裡,不就行了麼?

恩,定義乙個global[10000],然後執行緒1利用global[0],執行緒2利用global[9999],應該就可以了。

什麼?這麼愚蠢的方法都想得出來?接著往下看。

解決方法二:假如global不是乙個陣列呢?而是包含多個變數的結構體呢(這種情形也很常見)?上面的方法就不靈了吧?

恩,上面的方法不靈了,而且上面的方法太笨。網上有很多資料告訴您怎麼定義變數讓其cacheline aligned,這也是那些部落格千篇一律的做法,不過還是值得在這裡提及。

以gcc編譯器為例:下面的做法不能保證global[0]和global[1]不在同乙個cacheline裡:

1

2

#define cacheline_size 64

int32_t global[2] __attribute__((aligned(cacheline_size)));

這樣只能保證global陣列的首元素是cacheline對齊的,不能保證global[0]和global[1]不在同乙個cacheline。

以下是正確做法:

123

456

#define cacheline_size 64

struct mycounter

__attribute__((aligned (cacheline_size)));

mycounter global[2];

還有沒有其他方法?有。接著往下看。

解決方法三:重點來了。

123

4567

891011

1213

1415

1617

1819

2021

2223

2425

26

#includeint32_t global[2] = ;

void f()

global[0] = counter1;

}void g()

global[1] = counter2;

}int main()

counter1counter2在自己的執行緒棧上,cacheline位於對應的cpu core裡,大家相安無事。只有執行第9行和第17行時代價可能高點。

這個程式,在我的電腦上執行時間為:

real	0m0.293s

user 0m0.580s

sys 0m0.000s

解決方法四:

global神馬變數?全域性變數。counter1/counter2神馬變數?區域性變數。

有沒有一種東東,既有全域性的性質,又有區域性的效果(執行緒私有)呢?

恩,如果您看過我以前寫的這篇部落格,就不會對__thread感到陌生。對!提供強大scalability的利器,就是它了!

123

4567

891011

1213

1415

1617

1819

2021

22

#include__thread int counter = 0;

void f()

}void g()

}int main()

這個程式在我的電腦上的執行時間為:

real	0m0.325s

user 0m0.644s

sys 0m0.000s

不過其他執行緒如何讀取到每個計數執行緒的counter呢?不難,但是也不是那麼簡單,背後涉及到很多問題(其實本文最大的目的是通過false sharing,揭示partition這種併發程式設計裡最大的設計原則)。我們下次專門聊。

1,編譯以上多執行緒程式的時候,請使用:

g++ -pthread -std=c++11 ***.cpp
terminate called after throwing an instance of 『std::system_error』

what(): enable multithreading to use std::thread: operation not permitted

aborted (core dumped)

2,程式計時我用的是time ./a.out的方式。

詭異的C程式

include int main 結果是 you got died 解釋 1 night 就是 night 1 也就是 i ascii碼為105 類似,2 girls 是 l 114 4 allnight 是 i 105 result 105 114 105 256 43 result of you...

C 詭異程式

using system using system.collections.generic using system.windows.forms using system.runtime.interopservices using system.threading using system.medi...

詭異的電梯

時間限制 1000 ms 記憶體限制 65535 kb 難度 3 描述 新的宿舍樓有 n 1 n 100000 層and m 1 m 100000 個學生.在新的宿舍樓裡 為了節約學生的時間也為了鼓勵學生鍛鍊身體 所以規定該宿舍樓裡的電梯在相鄰的兩層之間是不會連續停下 即,如果在第2層停下就不能在第...