超線程SMT究竟可以快多少?(AMD Ryzen版 )
宋老師的SMT測試很有意思,但是編譯內(nèi)核涉及的因素太多了,包括訪問文件系統(tǒng)等耗時受到存儲器性能的影響,難以估算,因此很難評判SMT對性能的提升如何。
?為了探究SMT對計算密集型workload的效果,我自己寫了一個簡單的測試程序。?使用pthread開多個線程,每個線程分別計算斐波那契數(shù)列第N號元素的值。每個線程計算斐波那契數(shù)列時除線程的元數(shù)據(jù)外只分配兩個unsigned long變量,由此避免過高的內(nèi)存開銷。?workload的詳細代碼和測試腳本在[https://github.com/HongweiQin/smt_test]?毫無疑問,這是一個計算密集型負載,我在自己的筆記本上運行,配置如下(省略了一些不重要的項目):
$ lscpu
Architecture: x86_64
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
L1d cache: 192 KiB
L1i cache: 192 KiB
L2 cache: 1.5 MiB
L3 cache: 12 MiB
?可以看到筆記本有一個Intel i7的處理器,6核12線程。經(jīng)查,CPU0和CPU6共用一個Core,CPU1和CPU7共用一個Core,以此類推。?以下的測試(Test 1-5)中,每個線程分別計算斐波那契數(shù)列第40億號元素的數(shù)值。?Test1:采用默認配置,開12線程進行測試。測試結(jié)果為總耗時45.003s。?qhw@qhw-laptop:~/develop/smt_test$ time ./smt_test -f 4000000000
threads_num=12, fibonacci_max=4000000000, should_set_affinity=0, should_inline=1, alloc_granularity=32
?
real0m45.003s
user7m12.953s
sys0m0.485s
?Test2:把smt關掉,同樣的測試方法(12線程)??偤臅r為25.733s。?
qhw@qhw-laptop:~/develop/smt_test$ cat turnoff_smt.sh
#!/bin/bash
echo "turn off smt"
sudo sh -c 'echo off > /sys/devices/system/cpu/smt/control'
qhw@qhw-laptop:~/develop/smt_test$ ./turnoff_smt.sh
turn off smt
qhw@qhw-laptop:~/develop/smt_test$ time ./smt_test -f 4000000000
threads_num=12, fibonacci_max=4000000000, should_set_affinity=0, should_inline=1, alloc_granularity=32
?
real0m25.733s
user2m23.525s
sys0m0.116s
?對,你沒看錯。同樣的workload,如果關掉smt,總耗時還變少了。Intel誠不欺我!?Test3:再次允許smt,但是將程序限制在三個物理Core上運行,則總耗時為34.896s。?qhw@qhw-laptop:~/develop/smt_test$ ./turnon_smt.sh
turn on smt
qhw@qhw-laptop:~/develop/smt_test$ time taskset -c 0-2,6-8 ./smt_test -f 4000000000
threads_num=12, fibonacci_max=4000000000, should_set_affinity=0, should_inline=1, alloc_granularity=32
?
real0m34.896s
user3m17.033s
sys0m0.028s
Test3相比于Test1用了更少的Core,反而更快了。?為什么在Test2和3會出現(xiàn)這樣違反直覺的結(jié)果???猜想:Cache一致性在作怪!?圖1
測試程序的main函數(shù)會分配一個含有T(T=nr_threads)個元素的`struct thread_info`類型的數(shù)組,并分別將每個元素作為參數(shù)傳遞給每個計算線程使用。`struct thread_info`定義如下:?
struct thread_info {
pthread_t thread_id;
int thread_num;
unsigned long res[2];
};
?結(jié)構(gòu)體中的res數(shù)組用于計算斐波那契數(shù)列,因此會被工作線程頻繁地寫。?注意到,sizeof(struct thread_info)為32,而我的CPU的cacheline大小為64B!這意味著什么??圖2
如圖所示,如果Thread 0在Core 0上運行,則它會頻繁寫tinfo[0],Thread 1在Core 1上運行,則它會頻繁寫tinfo[1]。?這意味著,當Thread 0寫tinfo[0]時,它其實是寫入了Core 0上L1 Cache的Cacheline。同樣的,當Thread 1寫tinfo[1]時,它其實是寫入了Core 1上L1 Cache的Cacheline。此時,由于Core 1上的Cacheline并非最新,因此CPU需要首先將Core 0中的Cacheline寫入多核共享的L3 Cache甚至是內(nèi)存中,然后再將其讀入Core 1的L1 Cache中,最后再將Thread 1的數(shù)據(jù)寫入。此時,由于Cache 0中的數(shù)據(jù)并非最新,Cacheline會被無效化。由此可見,如果程序一直這樣運行下去,這一組數(shù)據(jù)需要在Cache 0和1之間反復跳躍,占用較多時間。?這個猜想同樣可以解釋為什么使用較少的CPU可以加速程序運行。原因是當使用較少的CPU時,多線程不得不分時共用CPU,如果Thread 0和Thread 1分時共用了同一個CPU,則不需要頻繁將Cache無效化,程序運行時間也就縮短了。??驗證猜想:增加內(nèi)存分配粒度!?對程序進行修改后,可以使用`-g alloc_granularity`參數(shù)設定tinfo結(jié)構(gòu)體的分配粒度。使用4KB為粒度進行分配,再次進行測試:?Test4:12線程,開啟SMT,分配粒度為4096。總耗時為13.193s,性能相比于Test1的45.003s有了質(zhì)的提升!?
qhw@qhw-laptop:~/develop/smt_test$ time ./smt_test -f 4000000000 -g 4096
threads_num=12, fibonacci_max=4000000000, should_set_affinity=0, should_inline=1, alloc_granularity=4096
?
real0m13.193s
user2m31.091s
sys0m0.217s
?Test5:在Test4的基礎上限制只能使用3個物理Core??偤臅r為24.841s,基本上是Test4的兩倍。這說明在這個測試下,多核性能還是線性可擴展的。?
qhw@qhw-laptop:~/develop/smt_test$ time taskset -c 0-2,6-8 ./smt_test -f 4000000000 -g 4096
threads_num=12, fibonacci_max=4000000000, should_set_affinity=0, should_inline=1, alloc_granularity=4096
?
real0m24.841s
user2m26.253s
sys0m0.032s
?超線程SMT究竟可以快多少??表格和結(jié)論:?
測試名? | 硬件配置? | 運行時間(s) |
Test6 | ?“真”6核?? | 38.562? |
Test7 | “假”6核? | 58.843 |
Test8 | “真”3核? | ?73.175? |
qhw@qhw-laptop:~/develop/smt_test$ cat test.sh
#!/bin/bash
fibonacci=20000000000
sudo printf ""
./turnoff_smt.sh
time ./smt_test -f $fibonacci -g 4096 -t 6
./turnon_smt.sh
time taskset -c 0-2,6-8 ./smt_test -f $fibonacci -g 4096 -t 6
./turnoff_smt.sh
time taskset -c 0-2,6-8 ./smt_test -f $fibonacci -g 4096 -t 6
./turnon_smt.sh
qhw@qhw-laptop:~/develop/smt_test$ ./test.sh
turn off smt
threads_num=6, fibonacci_max=20000000000, should_set_affinity=0, should_inline=1, alloc_granularity=4096
real0m38.562s
user3m50.786s
sys0m0.000s
turn on smt
threads_num=6, fibonacci_max=20000000000, should_set_affinity=0, should_inline=1, alloc_granularity=4096
real0m58.843s
user5m53.018s
sys0m0.005s
turn off smt
threads_num=6, fibonacci_max=20000000000, should_set_affinity=0, should_inline=1, alloc_granularity=4096
real1m13.175s
user3m39.486s
sys0m0.008s
turn on smt
更多精彩,點擊關注"Linux閱碼場"