在Linux系統(tǒng)中箱玷,進程的調(diào)度切換是由內(nèi)核自動完成的,在多核CPU上俭茧,進程有可能在不同的CPU核上來回切換執(zhí)行,這對CPU的緩存不是很有利漓帚。為什么呢母债?先看一張 Intel i5 CPU 的緩存簡單示意圖:
在多核CPU結(jié)構(gòu)中,每個核心有各自的L1、L2緩存毡们,而L3緩存是共用的迅皇。如果一個進程在核心間來回切換,各個核心的緩存命中率就會受到影響衙熔。相反如果進程不管如何調(diào)度登颓,都始終可以在一個核心上執(zhí)行,那么其數(shù)據(jù)的L1红氯、L2 緩存的命中率可以顯著提高框咙。
1. 如何設(shè)置進程與CPU核心綁定
在 Linux 系統(tǒng)里,可以使用 CPU_* 系列函數(shù)和 sched_setaffinity()
可以實現(xiàn)綁定痢甘,具體步驟如下:
- 使用 CPU_系列函數(shù)喇嘱,必須定義 _GNU_SOURCE 宏,告訴編譯器啟用這些函數(shù):
#define _GNU_SOURCE
- 首先聲明一個
cpu_set_t
塞栅,然后用CPU_ZERO()
初始化bit數(shù)據(jù):
cpu_set_t mask;
CPU_ZERO(&mask);
cpu_set_t
其實是一個bit串者铜,每個bit表示進程是否要與某個CPU核綁定。
- 接下來把進程綁定到某幾個CPU核心构蹬,這要用
CPU_SET()
來設(shè)置cpu_set_t中相應(yīng)的bit位王暗,比如想讓進程只在核心1或核心5上執(zhí)行:
CPU_SET(1, &mask);
CPU_SET(5, &mask);
- 最后用
sched_setaffinity
完成實際的綁定:
sched_setaffinity(0, sizeof(cpu_set_t), &mask);
設(shè)置起來并不難。那怎么驗證我們的綁定真的起作用了呢庄敛?我們來做個實驗:
假定有一臺雙核機器俗壹,這段程序我們起了20個進程,從0開始每個進程分配一個進程號(注意是這里值我們自己起的進程號藻烤,不是進程pid)绷雏,奇數(shù)進程號綁定綁定在 Core 0上執(zhí)行,偶數(shù)號的進程綁定在 Core 1上執(zhí)行怖亭。
我們用for
讓進程循環(huán)涎显,用 sched_getcpu()
函數(shù)獲得當前進程運行在哪個CPU核心上,每次for
循環(huán)檢查下進程是否真的在分配的核心執(zhí)行兴猩。
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void run(int c, int n) {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(n, &mask);
sched_setaffinity(0, sizeof(cpu_set_t), &mask);
int i;
for (i = 0; i != 10000; i++) {
printf("%d-%d\n", c, sched_getcpu());
}
}
int main()
{
int i;
for (i = 0; i != 20; i++) {
int pid = fork();
if (pid == 0) {
run(i, i % 2);
exit(0);
}
}
}
執(zhí)行上面的程序期吓,就會打印每個進程綁定的CPU核號,進程與核號的關(guān)系肯定不會變倾芝。如果把 sched_setaffinity()
注釋掉讨勤,CPU進程就失去綁定。
2. 設(shè)置親和性后的性能測試
設(shè)置了進程與CPU綁定后晨另,我們來看看是否能真的帶來性能的提升潭千。修改上面的run()
函數(shù),每個進程創(chuàng)建一個數(shù)組借尿,然后計算數(shù)組中值的累加刨晴,創(chuàng)建數(shù)組的意圖是保證進程用到了CPU核心的L1屉来、L2緩存:
void run(int c, int n) {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(n, &mask);
sched_setaffinity(0, sizeof(cpu_set_t), &mask);
struct timeval tv;
gettimeofday(&tv, NULL);
long begin = tv.tv_sec * 1000 + tv.tv_usec / 1000;
int i;
int arr[N];
for (i = 0; i != N; i++) {
arr[i] = i;
}
long sum = 0;
for (i = 0; i != N; i++) {
sum += arr[i];
}
gettimeofday(&tv, NULL);
long end = tv.tv_sec * 1000 + tv.tv_usec / 1000;
printf("%ld\n", end - begin);
}
然后執(zhí)行20次程序,10次沒有CPU綁定狈癞,10次有CPU綁定茄靠,記錄每個進程的耗時毫秒數(shù),就有下面的結(jié)果:
P1~P20是進程號亿驾,A1~A10列是沒有CPU綁定的情況嘹黔,B1~B10列是有CPU綁定的情況,耗時越久單元格越紅莫瞬。可見綁定了CPU的情況下性能有近10%的提升郭蕉。