最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【知乎】頻率測定,啟程!

2023-12-18 10:36 作者:失傳技術  | 我要投稿

頻率測定,啟程!


JamesAslan


喜歡畫畫和攝影的硅工碼農(nóng)(滑稽)

45 人贊同了該文章

前言

你將看到:定頻的方法論、程序實現(xiàn)細節(jié)、一些測試結果和12代酷睿逆天表現(xiàn)定頻翻車

首先我們開始熱身,嘗試通過程序來測定CPU的實時頻率。這里一定有很多人要問了,為什么要自己給CPU定頻呢?本來就有一萬種方法直接獲取處理器頻率,例如:

lscpu

還可以實時獲取每個核心的頻率:

cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq

但是一方面,在某些特殊環(huán)境下,用以上方式會讀取出錯誤的頻率信息,且這些命令不是全平臺通用的;另一方面,定頻程序有時能夠幫助我們獲得許多有意思的信息,在12代酷睿的測試中我們就會發(fā)現(xiàn)這一點。此外,這樣的程序也是一個練手的好機會。

方法論

OK,我們切入正題,如何測定頻率呢?基本思路:運行指定數(shù)量的指令n,測定一個時鐘周期運行指令的數(shù)量k,記錄它們的運行時間t;首先得到總時鐘周期數(shù)n/k,再得到每個時鐘周期的平均時長t/(n/k)=t*k/n,那么頻率就是這個值的倒數(shù)n/kt。聽起來很簡單,但是實際上有幾個關鍵的問題需要解決:

1.運行什么指令呢?在著名的LMBench中,使用了nop指令。但是使用nop指令有幾個明顯的缺點,時至2021年,大多數(shù)處理器都配備了對nop指令的前端消除,也就是說nop指令實際不會被執(zhí)行。這不利于我們控制處理器的行為,因為其nop消除寬度(每個周期能夠消除nop的數(shù)量)很可能和譯碼寬度、分派寬度、發(fā)射寬度、執(zhí)行寬度、提交寬度不同,我們不能預設某款處理器每個周期執(zhí)行nop指令的數(shù)量(即nop指令的吞吐量),這個值k需要實際測定,十分麻煩。有沒有什么指令的實際吞吐量不需要測定就能直接得知呢?我們立刻就能想到加法鏈!如果我們構造一條如下的加法指令的鏈條:

a += 1;a += 1;a += 1;a += 1;a += 1; ? ?

每次給一個數(shù)(寄存器)加1,那么即使是在亂序處理器中,這樣的真數(shù)據(jù)相關也會導致指令之間無法并行,換句話說,這樣的加法鏈條處理器每周期只能執(zhí)行一條指令,那么k=1!如果這個“假設”成立,那么這個公式中所需的a值無需測定,豈不是十分方便?接下來可以看到,這樣的假設在12代酷睿發(fā)布前確實成立,而12代酷睿的大核架構GoldenCove也許是近年來第一個打破此假設的設計。不妨假定此假設成立進行接下來的定頻。

2.如何測定運行時長?一款現(xiàn)代處理器的最大運行頻率往往在3GHz以上,意味著1s內(nèi)處理器已經(jīng)運行了3*10 ^9以上的時鐘周期數(shù)(cycle),即使每周期只能執(zhí)行1條指令,每秒也可以執(zhí)行超過3*10 ^9條指令。為了相對準確地獲得運行時長,我們需要保障一定的指令數(shù)以平攤一些意外事件的影響,包括但不限于:(1)時鐘中斷(操作系統(tǒng))(2)進程調(diào)度(操作系統(tǒng))(3)DVFS,動態(tài)調(diào)頻調(diào)壓的延遲(處理器)(4)Cache、TLB miss,首次執(zhí)行我們的程序時這些事件難以避免(處理器)(5)noisy neighbours,(沒錯,就是一邊測試一邊還用電腦摸魚的你)。直覺上也許我們會寫出如下代碼:

i=100000000000;do{ a += 1; ? i--;}while (i);

但是這里有明顯的問題,循環(huán)結構會產(chǎn)生指令,我們需要測定的加法a += 1也會產(chǎn)生指令,倘若一個循環(huán)內(nèi)只有一個加法,那么我們測量的顯然就不僅僅是這條加法指令的執(zhí)行時長了,不妨查閱反匯編(此處以x86平臺為例,實際上今后測試也常會在apple m1上同步進行):

13b7: 49 83 c4 01 ? ? ? ? ? add ? ?$0x1,%r12 13bb: 83 eb 01 ? ? ? ? ? ? sub ? ?$0x1,%ebx 13be: 85 db ? ? ? ? ? ? ? ? test ? %ebx,%ebx 13c0: 75 f5 ? ? ? ? ? ? ? ? jne ? ?13b7 <main+0x17c>

我們可以看到我們的測量目標就是

13b7: 49 83 c4 01 ? ? ? ? ? add ? ?$0x1,%r12

但是卻產(chǎn)生了許多無關的循環(huán)相關指令,甚至數(shù)量遠超add。在進行逆向時,無關的跳轉(也就是分支指令)和訪存是最大的敵人,一條跳轉和訪存指令時常會引入幾十乃至上千cycle的執(zhí)行時長,而我們的add指令執(zhí)行時長只有1cylce,因此這些指令對我們控制處理器的實際行為是相當不利的,也許在頻率測定時它們產(chǎn)生的影響可控,但是隨著測試深入,需要盡可能避免這樣的無關因素的干擾。

那么又有人會問了,我直接用python生成100000000000條加法指令,不使用循環(huán)了行不行呢?那么生成器的代碼和它生成的代碼如下:

//生成器 tmp = open("add", "w") for i in range(1,100000000000): ? ?tmp.write("a += 1; ") //生成的代碼 a += 1; ? a += 1; a += 1; a += 1; a += 1; ……………………………… 此處省略100000000000條 ……………………………… a += 1; a += 1; a += 1;

答案是也不行。需要時刻記住,每一條指令都會占據(jù)ICache的存儲空間,100000000000個“a += 1; ”意味著100000000000條pc(地址)不同的add指令,處理器難以容納如此多的代碼,那在沒有合適的指令預取器的情況下就會引入Cache miss,也就是最大的敵人之一:無關的訪存。因此我們需要結合循環(huán)和代碼生成器,在ICache能夠容納的前提下,盡可能得避免循環(huán)引入的跳轉指令。

實現(xiàn)細節(jié)

  1. 綁核!由于操作系統(tǒng)中調(diào)度器的存在,以及異構多核的盛行,我們需要盡可能得使得程序運行在我們需要的核心上,一般可以用如下方法:

//需要寫在程序里!cpu_set_t mask;CPU_ZERO(&mask);CPU_SET(0, &mask);sched_setaffinity(0, sizeof(cpu_set_t), &mask);

還有其他方便的方法(可以就此百度順藤摸瓜):

//test是我們的測試程序,使用taskset -c綁定在1號核上 taskset -c 1 ./test

在apple m1上有其他方式,今后如需使用再說明。

2.不想用python寫代碼生成器?OK,用#define神教來實現(xiàn)吧,今后我們還會多次使用這一方法:

#define ONE ? ? ? ? a += 1;#define TWO ? ? ? ? ONE ONE#define FOUR ? ? ? ?TWO TWO#define TEN ? ? ? ? FOUR FOUR TWO#define HUNDRED ? ? TEN TEN TEN TEN TEN TEN TEN TEN TEN TEN #define THOUSAND ? ?HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED#define TENTHOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND do{ ? TENTHOUSAND ? i--;} while (i);

3.關鍵時刻使用寄存器!為了避免引入不必要的訪存,我們將關鍵變量存儲在寄存器中,因此聲明變量時需要特別注意。另外,寄存器數(shù)量是有限的,無法將所有變量都放入寄存器中,需要仔細斟酌。

register long long a = 0; ?//加法對象使用寄存器register int i = 1000000; //循環(huán)變量使用寄存器do{ ? TENTHOUSAND ? i--;} while (i);

4.計時,方式多種多樣,根據(jù)程序運行時長的區(qū)別使用合適的即可,注意單位

struct timespec begin,end;register long long a = 0;register int i = 1000000;clock_gettime(CLOCK_REALTIME,&begin);do{ ? TENTHOUSAND ? i--;} while (i);clock_gettime(CLOCK_REALTIME,&end);printf("%f", (10000000000) ?/ (end.tv_sec - begin.tv_sec + (end.tv_nsec - begin.tv_nsec)/1.0e9) / 1000000000);

或者gettimeofday()等等。

5.和編譯器斗智斗勇!2021年,大部分編譯器都不會對你寫出這樣的程序來浪費電的行為坐視不管,很可能會直接將這整個循環(huán)優(yōu)化掉?。创a消失)。因為雖然我們不停得給a加1,但是最終的結果卻無人使用,因此我們需要在函數(shù)返回時動些手腳,加入:

struct timespec begin,end;register long long a = 0;register int i = 1000000;clock_gettime(CLOCK_REALTIME,&begin);do{ ? TENTHOUSAND ? i--;} while (i);clock_gettime(CLOCK_REALTIME,&end);printf("%f", (10000000000) ?/ (end.tv_sec - begin.tv_sec + (end.tv_nsec - begin.tv_nsec)/1.0e9) / 1000000000);return a == 0;//使用了a的數(shù)值!欺騙編譯器

測試結論

Core i3 9100f(Skylake)

由于是純c代碼,直接遷移到其他平臺也是可以的,不妨到arm上試一試:

Apple M1 (Firestorm)

完美,一切正常。試一試最新的i7 12700k(Goldencove Gracemont):

恩,很正……What the f***? Goldencove 怎么可能運行在21 GHz呢?可是在E core上運行正常說明編譯器并沒有將代碼整體優(yōu)化,那么究竟發(fā)生了什么呢?

說明我們的假設失效了:

這樣的加法鏈條處理器每周期只能執(zhí)行一條指令

這是一個關于指令融合的故事,我曾糾結許久為什么沒有廠商進行此類優(yōu)化,沒想到這個定頻程序偶然地揭示了12代酷睿上的小秘密,下回就讓我們來仔細探究一下Goldencove中的加法融合是如何實現(xiàn)的。


編輯于 2023-02-04 11:33


【知乎】頻率測定,啟程!的評論 (共 條)

分享到微博請遵守國家法律
太白县| 岑溪市| 麟游县| 嵊州市| 清河县| 罗江县| 镇原县| 和平区| 黔东| 工布江达县| 江口县| 贺兰县| 观塘区| 房产| 乌鲁木齐市| 酉阳| 绥德县| 洞头县| 平塘县| 龙陵县| 四会市| 齐河县| 抚远县| 清徐县| 安塞县| 长丰县| 时尚| 乌什县| 吴川市| 宾川县| 信丰县| 定襄县| 道孚县| 郎溪县| 塔城市| 平乡县| 汉寿县| 清徐县| 清水河县| 凭祥市| 光山县|