linux:多線程進程比單線程進程的性能要差?

問題解決:請見陳碩的回答,問題的最後有關於false sharing的參考文檔。

以下是正文

**************************

請原諒我的標題。

先說測試結論:特定情況下,在雙路多核伺服器環境下,多線程進程相對單線程進程下對性能的提升有限

環境:

2*Intel Xeon E5645 六核心,共24線程

128GB 內存

測試系統:REHL 6.3與 ubuntu server 14.04

測試用例:

單線程:隨機查詢含有500W元素的HASH表(很稀疏、加滿500W元素後占幾百MB內存),匹配欄位為一個int(預先生成一個隨機數組,所以不要懷疑是rand函數的原因);無鎖(無mutex、無spinlock);

多線程:同上,每個線程單獨對應一個HASH,無衝突;

測試結果:

單線程進程每秒完成查詢500w次;

16線程進程每秒完成查詢1000w次,每個線程完成約62.5w次查詢

REHL和 ubuntu server表現基本相同。

然而,為何我同時打開四個單線程進程就能達到超過1600w pps的性能?線程不是輕量級的進程嗎?

PS:我在一台ubuntu desktop 12.04的台式機上(i5、4線程)多線程進程表現要好很多,4線程基本上是單線程的4倍。

不好意思,讓大家久等了!

先貼最新的測試結果

以下結果未設置CPU和thread的affanity,設置後有所提升,但差別不大。

圖1

鎖加的是pthread_mutex_t,可在代碼的「/***1.是否加鎖****/」下打開

圖2

PC配置:1* Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz ubuntu desktop 12.04

伺服器配置:聯想 2*Intel Xeon E5645 六核心,24線程 ubuntu server 14.04,在另外一台DELL的雙路六核伺服器上取得了相近的結果。

如下是代碼,我對原有的程序進行了精簡,但不影響結果:

見我的回答

感謝諸位,在陳碩,3樓的匿名用戶、pig pig、teawater的建議下,我關掉了超線程、在石晉的建議下我使用numactl --cpubind=0 --membind=0 ./test_pthread設置node和內存的親緣性(numactl --hardware顯示兩個node,每個node對應8GB的內存,抱歉忘記截圖了)。另,不關超線程,單純設置CPU內存親緣性對性能提升有限。

關掉伺服器的超線程後,結果如下:

圖3

圖4

使用numactl --cpubind=0 --membind=0 ./test_pthread 啟動後,8線程下CPU情況如下:

額,目前還不知道如何設置才能利用好另外一路CPU。

********************0926更新*******************

感謝大牛陳碩這個問題確是由false sharing引起的,在編寫這個demo的時候意識到這樣可能有問題,但沒有想到影響居然這麼大。另外,demo里加鎖不能起到保護作用,我只是順便測試一下不同鎖的性能,不要效仿。

以上這段代碼中影響性能的主要是static volatile unsigned long op[100]和static volatile int g_rand = 0;其中前者造成了false sharing,而後者不應該使用、除非g_rand很少被修改

若進行簡單修改(若不對請批評指正),static volatile unsigned long op[100]可以改為static volatile unsigned long op[100][N],其中N滿足N*sizeof(unsigned long) == 64,若嫌不保險,可以使N*sizeof(unsigned long) &> 128。

關於false sharing的文檔有如下參考文獻:

【1】Avoiding and Identifying False Sharing Among Threads

【2】Concurrency Hazards: False Sharing


貼代碼。

目前猜測你看到的現象是Westmere和Sandy Bridge的區別。

----

有代碼就好說了,明顯是false sharing造成的問題。

我把代碼修改了一下,基本去掉false sharing:

struct thread_info
{
pthread_t tid;
int id;
unsigned seed;
unsigned* hash;
int64_t count;
};

const int HASH_SIZE = 5 * 1024 * 1024;
const int64_t TOTAL_OPS = 1000 * 1000 * 1000;

double now()
{
struct timeval tv;
gettimeofday(tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}

void* thread_func(void* arg)
{
struct thread_info ti = *(struct thread_info*)arg;
printf("%f thread: %d started
", now(), ti.id);

for (int64_t i = 0; i &< ti.count; ++i) { unsigned int obj = rand_r(ti.seed); ti.hash[obj % HASH_SIZE] = obj; // rand_r(ti.seed); } printf("%f thread: %d finished ", now(), ti.id); return NULL; } int main(int argc, char* argv[]) { struct thread_info thread_infos[32]; bzero(thread_infos, sizeof thread_infos); int num = sysconf(_SC_NPROCESSORS_CONF); printf("system has %d processor(s) ", num); srand(time(NULL)); int nThreads = argc &> 1 ? atoi(argv[1]) : 1;
double start = now();
printf("%f creating %d threads
", start, nThreads);
for (int i = 0; i &< nThreads; i++) { thread_infos[i].id = i; thread_infos[i].seed = rand(); thread_infos[i].hash = calloc(HASH_SIZE, sizeof(unsigned int)); thread_infos[i].count = TOTAL_OPS; pthread_create(thread_infos[i].tid, NULL, thread_func, thread_infos[i]); } for (int i = 0; i &< nThreads; i++) { pthread_join(thread_infos[i].tid, NULL); } double end = now(); double seconds = end - start; printf("%f all threads stopped, elapsed %f seconds ", end, seconds); printf("total_ops/s = %.3f M ops/s for each thread = %.3f M ", TOTAL_OPS / seconds / 1000000 * nThreads, TOTAL_OPS / seconds / 1000000); return 0; }

在單路X5650,6核12線程上測試數據如下:

可見在5個線程以內大致是線性的。注意性能瓶頸在 rand_r(),如果去掉它,改成 unsigned int obj = ti.seed + i; ,性能高10倍。另外你的加鎖是錯的,沒有起到保護共享數據的作用。


下次問這種問題先貼個profile的報告行不?

順便補一句,profile數據都沒看就猜來猜去的,基本上都是外行。你用的是Intel平台,oprofile,perf,itune,隨便哪個都能給你很好的結果,你們這樣猜來猜去是要鬧哪樣?


個人猜測:並發線程過多緩存命中率下降。

你有沒有試過單進程四線程?


第一是CACHE的問題 多TASK會有影響

還有個問題CPU有硬線程 同一個核上的硬線程互相影響我也是觀察到過 你可以看看關掉硬線程 或者強制把TASK鎖到不同的核上 看看效果


實驗設計不夠清晰,得不到有效的結論,我覺得需要重新設計實驗。我能想到影響這種環境下多線程性能的影響因素,從體系結構的角度出發,大概有: SMT,緩存一致性, DVFS,緩存共享等方面。具體來說,E5645這個處理器通過HT的話才有12個線程,超線程出來的線程不能簡單視為的12個完全相同的硬體線程;緩存一致性來說,你的線程數目大於單個E5645的線程數目,基本可以確定線程會分布在兩個處理器上運行,這樣一致性的開銷將會變大;動態頻率調整,這個先不說了,和我正在做的研究相關;緩存共享,一方面假共享,另一方面就是對緩存資源的競爭。 如果真的想做對比,我的建議是,首先實現一個無鎖無同步的版本,禁用超線程和DVFS,線程和core進行綁定,線程數目不要超過6,實驗內容要包括多進程和多線程,n進程的性能應該會小於一個進程性能的n倍的,所以你比較的時候不能簡單的從一個進程推算n個進程。


我說的不一定對,不過我是這樣認為的:

我們的代碼編寫只是在高級語言層次,而不是彙編層次,經過代碼優化以及機器碼生成之後,線程之間是否存在互相鎖定,這在高級語言層次不完全可控。要寫出完全不互相影響的多線程代碼或許需要滿足很多特定條件。

線程之間需要互相共享內存空間,因此如果兩個 CPU 核心跑一個進程中的兩個線程時,對內存的訪問必然涉及到在兩個 CPU 核心之間的同步,這使得兩個 CPU 核心無法完全獨立的去執行任務。——所以單進程多線程必然將影響多核 CPU 的調度效率。

而多進程單線程就沒有這個問題,不同的CPU核心完全使用的不同的內存空間。

多線程是輕量級進程應該是僅僅對內存佔用而言的。但在主流的非同步框架中,線程與進程的數量都限制在了接近 CPU 核心數量的程度,如果只按照核心數量開進程,進程的內存佔用問題已經完全不再成為問題。同時對於多核 CPU 而言,多線程存在額外的負擔。在多核時代,或許進程才是更輕量級的存在。

就我個人的實踐而言,只在「絕對必要」的情況下才使用單進程多線程。否則都應當使用多進程。


這不是Linux多線程或單線程的問題,而是題主自己的問題。

1.題主表述不清楚,出問題很正常。

題主沒表述清楚其使用什麼語言、環境、框架、IDE等。試想,多線程下的C++與Python,能一樣嗎?

題主沒表述HASH表的數據結構、內存申請方式、數據填充形式、匹配操作的流程細節、單線程的流程細節、多線程的流程細節等等。試想,就拿線程綁定CPU邏輯核來說,特殊情況下大部分線程都綁定到同一個核上,速度上的去嗎?

2.題主水平有硬傷,出問題很正常。

只讀式的匹配,是不需要鎖的。為什麼題主會提到【無鎖(無mutex、無spinlock)】?為什麼題主會提到【每個線程單獨對應一個HASH,無衝突】?

3.建議:

整個工程打包發上來,有什麼問題自然一目了然。


1)上代碼

2)perf 一下看看


各有優缺點及其適用的場景,起這樣的標題既不能肯定也不能否定,有亂扣帽子的嫌疑。


Memory wall (cpu快內存慢,瓶頸在內存). 可以設一下cpu和memory的affanity,可能會得到進一步的結論。


代碼:

static volatile unsigned long op[100];

static volatile int g_rand = 0;

static volatile unsigned long pre_op[100];

/*用於測試的不同鎖*/
pthread_mutex_t g_mutex[100];
pthread_rwlock_t g_mutex_rw[100];
pthread_spinlock_t g_spinlock[100];
/*500W的Hash表*/
#define HASH_SIZE (5*1024*1024)
static unsigned int *g_hash[32];
/*測試線程
static void *thread_select(void *arg)
{
int i = * (int *)arg;
printf("thread:%d
",i);
cpu_set_t mask;
cpu_set_t get;
char buf[256];
int num = sysconf(_SC_NPROCESSORS_CONF);
printf("system has %d processor(s)
", num);
CPU_ZERO(mask);
CPU_SET(i, mask);
/*設置線程的CPU親緣性。
*/
// if (pthread_setaffinity_np(pthread_self(), sizeof(mask), mask) &< 0) { // fprintf(stderr, "set thread affinity failed "); // } // unsigned int obj = rand(); while(1) { /***1.是否加鎖****/ // pthread_mutex_lock(g_mutex[i]); // pthread_spin_lock(g_spinlock[i]); // pthread_rwlock_rdlock(g_mutex_rw[i] ); // time(NULL); // rand(); // 操作計數 op[i] ++; unsigned int obj = g_rand; memcmp(obj, g_hash[i] + (g_rand % HASH_SIZE ) , sizeof(unsigned int ) ); // pthread_rwlock_unlock(g_mutex_rw[i] ); // pthread_spin_unlock(g_spinlock[i]); // pthread_mutex_unlock(g_mutex[i]); } } /*列印線程*/ void *thread_print(void *arg) { int n; while(1) { /*列印前四個線程當前總的操作計數*/ // printf("op[3]:%lu ",op[3]); printf("thread0_op_times: %lu pps thread1_op_times: %lu pps thread2_op_times: %lu pps thread3_op_times: %lu pps ", op[0], op[1] ,op[2], op[3] ); unsigned long all_op = 0; int j = 0; for(j = 0 ;j &< 32; j ++) { // pthread_mutex_lock(g_mutex[j]); all_op += (op[j] - pre_op[j]); pre_op[j] = op[j]; // pthread_mutex_unlock(g_mutex[j]); } /*列印休眠周期內的總操作次數*/ printf("all_op:%lu ", all_op ); all_op = 0; /*休眠*/ sleep(10); // usleep(1000 * 990); } return NULL; } void *thread_rand(void *arg) { while(1) { g_rand = rand(); } } int main(void) { pthread_t t[40]; int i = 0; int thread_id[32]; int total_num = 0; /*初始化隨機數種子 */ srand(time(NULL)); /*初始化鎖與計數 */ /*啟動隨機數生成線程*/ pthread_create(t[34], NULL, thread_rand, NULL); for( i = 0 ; i &< 32; i ++) { op[i] = 0; pre_op[i] = 0; pthread_mutex_init(g_mutex[i],NULL); pthread_rwlock_init(g_mutex_rw[i], NULL); pthread_spin_init(g_spinlock[i],0); g_hash[i] = (unsigned int *)malloc( sizeof(unsigned int ) * HASH_SIZE ); memset(g_hash[i],0, sizeof(unsigned int ) * HASH_SIZE ); } printf("start create thread_select "); /*線程優先順序*/ struct sched_param param; param.__sched_priority = 20; pthread_attr_t attr; pthread_attr_init(attr); pthread_attr_setinheritsched(attr,PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(attr,SCHED_FIFO); pthread_attr_setschedparam(attr,param); /*啟動列印線程*/ pthread_create(t[35], NULL, thread_print, NULL); /*線程編號*/ for( i=0;i&<32;i++){ thread_id[i]=i; } /***在這裡修改線程數****/ for(i=0;i &< 1;i++){ /*是否設置線程的優先順序*/ pthread_create(t[i], NULL, thread_select, (void *)thread_id[i]); //// pthread_create(t[i], attr, thread_select, (void *)thread_id[i]); } while(1) { sleep(1); } return 0; }


昨晚睡得晚,現在起來看到答案有點醉……

樓主啊,rand()函數不是並發的……多線訪問是有鎖的……額外的這個開銷導致你看到的狀態……


1、多核之間同步一級二級三級緩存數據;

2、多線程的上下文切換;

3、緩存行數據的競爭;


多線程對臨界變數加入多少鎖,然後還各種wait


實驗做得不夠清晰,樓主應該對比的是同樣N個線程,放在N個進程內和放在一個進程內的區別,建議做一系列的N,拿出數據再來討論。

另外,linux以線程為單位調度,想來應該不會有什麼區別。


如果線程間沒有鎖的話應該是一樣的


隨機訪問,所以沒cache什麼事。

Thread共用一個memory space。

Process每個有單獨的memory space。

memory也要一個一個去supply data,所以4個人做事肯定要比1個人快阿。


推薦閱讀:

多線程無並發用到共享資源,還需要加鎖嗎?
單線程中加鎖代碼的性能如何?
redis為什麼是單線程?在多核處理器下對主存的訪問真的比多線程更有效率?未來有可能改用多線程嗎?
malloc和free是線程安全的嗎,在多線程開發時用這兩個函數應該注意什麼?
Python有GIL為什麼還需要線程同步?

TAG:編程 | Linux | 伺服器 | 多線程 |