日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

c语言一行代码太长,C语言修改一行代码,运行效率居然提升数倍,这个技巧你知道吗...

發(fā)布時(shí)間:2025/5/22 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c语言一行代码太长,C语言修改一行代码,运行效率居然提升数倍,这个技巧你知道吗... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

對(duì)編譯、鏈接、OS內(nèi)核、系統(tǒng)調(diào)優(yōu)等技術(shù)感興趣的童鞋,不妨右上角關(guān)注一下吧,近期會(huì)持續(xù)更新相關(guān)方面的專題文章!引言

近日,網(wǎng)上看到一篇文章,分析數(shù)組訪問(wèn)的性能問(wèn)題。文章經(jīng)過(guò)一系列“有理有據(jù)”的論證之后,居然得出結(jié)論:訪問(wèn)數(shù)組的任意一個(gè)元素,程序性能上沒(méi)有任何差異。

看到這里,我徹底凌亂了!

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-1.jpg (29.91 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

真的沒(méi)有差異嗎?還是用數(shù)據(jù)說(shuō)話吧!

注:為了盡可能把來(lái)龍去脈講清楚,篇幅稍長(zhǎng),請(qǐng)耐心看下去,相信你會(huì)有收獲!

實(shí)例一 多維數(shù)組交換行列訪問(wèn)順序

這是演示Cache對(duì)程序性能影響的經(jīng)典例子:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-2.jpg (45.84 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

array1.c 和 array2.c

兩個(gè)程序只有一行差異:

第一個(gè)程序?qū)?shù)組按行進(jìn)行訪問(wèn)第二個(gè)程序?qū)?shù)組按列進(jìn)行訪問(wèn)

測(cè)試環(huán)境

OS / CPU:Ubuntu 19.04 / Xeon Gold 6130 2.10GHz

兩個(gè)比較關(guān)鍵的參數(shù):

Cache size:22MB

Cache line :64 字節(jié)

具體參數(shù)如下圖所示:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-3.jpg (88.3 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

cpuinfo

編譯

使用GCC編譯,使用默認(rèn)優(yōu)化級(jí)別。如下圖所示:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-4.jpg (23.24 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

編譯

運(yùn)行

用time命令測(cè)量一下兩個(gè)程序性能差異。運(yùn)行結(jié)果如下圖:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-5.jpg (38.1 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

運(yùn)行結(jié)果

結(jié)果

從測(cè)試結(jié)果看,第一個(gè)程序運(yùn)行花費(fèi)0.265秒,第二個(gè)花費(fèi)1.998秒。第二個(gè)程序消耗的時(shí)間居然是第一個(gè)程序的7.5倍!

這是為什么呢?當(dāng)然是因?yàn)镃ache!后面進(jìn)行解釋。

我們?cè)賮?lái)看一個(gè)多線程的例子。

實(shí)例二 多線程訪問(wèn)數(shù)據(jù)結(jié)構(gòu)的不同字段

這個(gè)例子中,我們定義個(gè)全局結(jié)構(gòu)體變量 data,然后創(chuàng)建兩個(gè)線程,分別訪問(wèn)data的兩個(gè)字段data.a和data.b。

兩個(gè)程序的線程實(shí)現(xiàn)代碼如下:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-6.jpg (62.9 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

thread1.c(左) 和 thread2.c(右)

main()函數(shù)很簡(jiǎn)單,只是創(chuàng)建兩個(gè)線程:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-7.jpg (48.75 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

main()函數(shù)

兩個(gè)例子中唯一的不同之處是:

第一個(gè)程序中,字段a和字段b是緊挨著的第二個(gè)程序中,字段a和字段b中間有一個(gè)大小為64個(gè)字節(jié)的字符數(shù)組。

測(cè)試環(huán)境和第一個(gè)例子一樣。

編譯

創(chuàng)建線程使用到了pthread庫(kù),因此編譯時(shí)需要加上 -lpthread。

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-8.jpg (28.54 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

編譯

運(yùn)行

同樣使用time命令測(cè)量?jī)蓚€(gè)程序的執(zhí)行時(shí)間,結(jié)果如下圖所示:

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-9.jpg (39.02 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

執(zhí)行結(jié)果

結(jié)果

從測(cè)試結(jié)果看,第一個(gè)程序消耗的時(shí)間是第二個(gè)程序的3倍!

這又是為什么?依舊是Cache!

在解釋具體原因之前,先簡(jiǎn)單介紹一些關(guān)于計(jì)算機(jī)存儲(chǔ)的基礎(chǔ)知識(shí)。

存儲(chǔ)金字塔

“存儲(chǔ)金字塔”這個(gè)詞,大家應(yīng)該都不陌生吧,它指的是現(xiàn)代計(jì)算機(jī)系統(tǒng)的分級(jí)存儲(chǔ)器體系結(jié)構(gòu)。

簡(jiǎn)單來(lái)說(shuō),就是離CPU越近的存儲(chǔ)器訪問(wèn)速度越快,但是生產(chǎn)成本越高,因此容量就越小。而離CPU越遠(yuǎn)的存儲(chǔ)器訪問(wèn)越慢,但是成本越低,因此容量就越大。

它看起來(lái)就像一個(gè)金字塔一樣,這就是“存儲(chǔ)金字塔”這個(gè)詞的由來(lái)。

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-10.jpg (38.67 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

存儲(chǔ)金字塔

最頂端,離CPU最近的是寄存器,它的訪問(wèn)速度最快,容量也最小,現(xiàn)代的CPU一般最多只有幾十個(gè)內(nèi)置寄存器。

最底端,離CPU最遠(yuǎn)的是網(wǎng)絡(luò)存儲(chǔ)設(shè)備,既然要通過(guò)網(wǎng)絡(luò)進(jìn)行訪問(wèn),可想而知,它的速度肯定是最慢的,但是容量卻幾乎不受限制。尤其隨著近年來(lái)云計(jì)算的蓬勃發(fā)展,我們的很多數(shù)據(jù)都是存儲(chǔ)在云端,分布在世界各地。

我們可以簡(jiǎn)單的認(rèn)為,高一級(jí)的存儲(chǔ)器是低一級(jí)存儲(chǔ)器的緩存。也就是把低一級(jí)層存儲(chǔ)器中最經(jīng)常被訪問(wèn)的數(shù)據(jù),存放在高一層的存儲(chǔ)器中,因?yàn)樗xCPU更近,訪問(wèn)速度更快。CPU每次訪問(wèn)數(shù)據(jù)時(shí),首先在高一級(jí)存儲(chǔ)器中查找,如果數(shù)據(jù)存在,就可以直接訪問(wèn),否則需要到低一級(jí)的存儲(chǔ)器中去查找。

這種金字塔式的存儲(chǔ)結(jié)構(gòu)之所以能夠很好的工作,得益于計(jì)算機(jī)程序的局部性原理。

局部性原理

一個(gè)設(shè)計(jì)優(yōu)良的計(jì)算機(jī)程序通常具有很好的局部性,包括時(shí)間局部性和空間局部性。

時(shí)間局部性:如果一個(gè)數(shù)據(jù)被訪問(wèn)過(guò)一次,那么很有可能它會(huì)在很短的時(shí)間內(nèi)再次被訪問(wèn)。空間局部性:如果一個(gè)數(shù)據(jù)被訪問(wèn)了,那么很有可能位于這個(gè)數(shù)據(jù)附近的其它數(shù)據(jù)也會(huì)很快被訪問(wèn)到。

一般來(lái)說(shuō),具有良好局部性的程序會(huì)比局部性較差的程序運(yùn)行的更快,程序性能更好。

數(shù)組就是一種把局部性原理利用到極致的數(shù)據(jù)結(jié)構(gòu),后面會(huì)詳細(xì)說(shuō)明。

高速緩存存儲(chǔ)器 - Cache

我們知道,程序在執(zhí)行之前,必須要先加載到內(nèi)存(DRAM主存儲(chǔ)器)中,然后數(shù)據(jù)和指令才能被CPU訪問(wèn)。

但是,由于CPU和內(nèi)存訪問(wèn)速度之間存在著幾個(gè)數(shù)量級(jí)的巨大的差距,如果CPU每次都要從內(nèi)存中去讀取數(shù)據(jù)的,就會(huì)導(dǎo)致大量的計(jì)算資源閑置,這對(duì)現(xiàn)代CPU是不可接受的。

為了解決這個(gè)問(wèn)題,在CPU和內(nèi)存之間設(shè)計(jì)了高速緩存存儲(chǔ)器,即Cache。

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-11.jpg (46.1 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

現(xiàn)代的CPU一般都有三級(jí)或者更多級(jí)的Cache,離CPU最近的是L1Cache(一級(jí)緩存),然后是L2 Cache、L3 Cache。L1 Cache的訪問(wèn)速度幾乎和寄存器一樣快,容量也最小, L3速度最慢,但容量最大。

這樣一來(lái),CPU在讀取數(shù)據(jù)時(shí),就會(huì)先逐級(jí)在Cache中查找,如果找到就直接從Cache讀取,找不到則從內(nèi)存中讀取。

在Cache中找到所需的數(shù)據(jù)被稱為命中(Cache hit),找不到則稱為未命中(Cache miss)。

Cache miss的時(shí)候,CPU就不得不直接從內(nèi)存中訪問(wèn)數(shù)據(jù),會(huì)面臨嚴(yán)重的performance懲罰。因此Cache miss率比較高的程序,performance會(huì)比較差。

Cache Line

Cache Line 可以理解為是 Cache和內(nèi)存之間進(jìn)行數(shù)據(jù)傳輸?shù)淖钚挝弧?/p>

很多現(xiàn)代CPU的Cache line大小是64個(gè)字節(jié),我所用的測(cè)試環(huán)境Cache Line大小就是64個(gè)字節(jié)。也就是說(shuō)每次數(shù)據(jù)在Cache和內(nèi)存之間傳輸,并不是一個(gè)字節(jié)一個(gè)字節(jié)進(jìn)行傳輸?shù)?#xff0c;而是以Cache Line為單位進(jìn)行傳輸?shù)摹?/p>

比如下面這個(gè)數(shù)組:

int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

假設(shè)數(shù)組a的的起始地址在內(nèi)存中是Cache Line對(duì)齊的(簡(jiǎn)單理解就是數(shù)組a的起始地址能被64整除),假如我們執(zhí)行下面的代碼:

int k = a[0][0];

在x86機(jī)器上,int是4個(gè)字節(jié),在把a(bǔ)[0][0]賦值給k時(shí),會(huì)把一個(gè)Cache line大小的數(shù)據(jù)從內(nèi)存加載到Cache中,64/4 = 16個(gè)int,也就是說(shuō)整個(gè)數(shù)組都被加載進(jìn)了Cache中。如果接下來(lái)一條指令繼續(xù)訪問(wèn)數(shù)組a的某個(gè)元素的話,就可以直接訪問(wèn)Cache的內(nèi)容。

Cache 一致性

在多CPU的系統(tǒng)中,每個(gè)CPU都有自己的本地Cache。因此,同一個(gè)地址的數(shù)據(jù),有可能在多個(gè)CPU的本地 Cache 里存在多份拷貝。

為了保證程序執(zhí)行的正確性,就必須保證同一個(gè)變量,每個(gè)CPU看到的值都是一樣的。也就是說(shuō),必須要保證每個(gè)CPU的本地Cache中能夠如實(shí)反映內(nèi)存中的真實(shí)數(shù)據(jù)。

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-12.jpg (44.73 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

假設(shè)一個(gè)變量在CPU0和CPU1的本地Cache中都有一份拷貝,當(dāng)CPU0修改了這個(gè)變量時(shí),就必須以某種方式通知CPU1,以便CPU1能夠及時(shí)更新自己本地Cache中的拷貝,這樣才能在兩個(gè)CPU之間保持?jǐn)?shù)據(jù)的同步。

注:現(xiàn)代CPU為了保證Cache一致性,都實(shí)現(xiàn)了非常復(fù)雜的Cache一致性協(xié)議,如MESI等。篇幅有限,這里不再贅述,以后會(huì)更新專門的文章進(jìn)行講解,有興趣的童鞋不妨關(guān)注一下。

需要注意的是,CPU之間的這種同步,是有很大開(kāi)銷的。這其實(shí)也是案例二的主要原因,后面會(huì)進(jìn)行說(shuō)明。

了解存儲(chǔ)金字塔和Cache的背景知識(shí)后,現(xiàn)在我們分析一下前面的兩個(gè)案例。

案例一原因分析

案例一中,兩個(gè)程序都是對(duì)一個(gè)同樣大小的數(shù)組逐個(gè)元素進(jìn)行賦值。唯一的區(qū)別是:

第一個(gè)程序?qū)?shù)組按行進(jìn)行賦值第二個(gè)程序?qū)?shù)組按列進(jìn)行賦值

為什么程序運(yùn)行效率差距竟有7倍之大呢?

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-13.jpg (27.53 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

我們知道,數(shù)組元素存儲(chǔ)在地址連續(xù)的內(nèi)存中,多維數(shù)組在內(nèi)存中是按行進(jìn)行存儲(chǔ)的。

第一個(gè)程序按行訪問(wèn)某個(gè)元素時(shí),該元素附近的一個(gè)Cache Line大小的元素都會(huì)被加載到Cache中,這樣一來(lái),在訪問(wèn)緊挨著的下一個(gè)元素時(shí),就可以直接訪問(wèn)Cache中的數(shù)據(jù),不需要再?gòu)膬?nèi)存中加載數(shù)據(jù)。也就是說(shuō),對(duì)數(shù)組按行進(jìn)行訪問(wèn)時(shí),具有更好的空間局部性, Cache命中率更高。

第二個(gè)程序按列訪問(wèn)某個(gè)元素時(shí),雖然該元素附近的一個(gè)Cache Line大小的元素也會(huì)被加載進(jìn)Cache中,但是程序接下來(lái)要訪問(wèn)的數(shù)據(jù)卻不是緊挨著的那個(gè)元素,因此很有可能會(huì)再次產(chǎn)生Cache miss,而不得不從內(nèi)存中加載數(shù)據(jù)。

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-14.jpg (34.56 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

而且,雖然Cache中會(huì)盡量保存最近訪問(wèn)過(guò)的數(shù)據(jù),但由于Cache大小有限,當(dāng)Cache被占滿時(shí),就不得不把一些數(shù)據(jù)給替換掉。這也是空間局部性差的程序更容易產(chǎn)生Cache miss的重要原因之一。

案例二原因分析

案例二中,兩個(gè)程序都有兩個(gè)線程,每個(gè)線程分別訪問(wèn)一個(gè)結(jié)構(gòu)體變量的不同字段。唯一的區(qū)別是,

第一個(gè)程序中,字段a和字段b是緊挨著的。第二個(gè)程序中,字段a和字段b中間有一個(gè)大小為64個(gè)字節(jié)的字符數(shù)組。

這其實(shí)涉及到Cache Line的偽共享(false sharing)問(wèn)題。

Cache Line偽共享

所謂Cache Line 偽共享,是由于運(yùn)行在不同CPU上的不同線程,同時(shí)修改處在同一個(gè)Cache Line上的數(shù)據(jù)引起的。

雖然在每個(gè)CPU看來(lái),各自修改的是不同的變量,但是由于這些變量在內(nèi)存中彼此緊挨著的,因此它們處于同一個(gè)Cache Line上。一個(gè)CPU修改這個(gè)Cache Line之后,為了保證Cache數(shù)據(jù)的一致性,必然導(dǎo)致另一個(gè)CPU的本地Cache的無(wú)效,因而觸發(fā)Cache miss,然后從內(nèi)存中重新加載變量被修改后的值。

多個(gè)線程頻繁的修改處于同一個(gè)Cache Line的數(shù)據(jù),會(huì)導(dǎo)致大量的Cache miss,因而造成程序性能大幅下降。

原因分析

C語(yǔ)言修改一行代碼,運(yùn)行效率居然提升數(shù)倍,這個(gè)技巧你知道嗎-15.jpg (27.84 KB, 下載次數(shù): 0)

2020-9-16 21:52 上傳

第一個(gè)程序中,字段a和字段b處于同一個(gè)Cache Line上,當(dāng)兩個(gè)線程同時(shí)修改這兩個(gè)字段時(shí),會(huì)觸發(fā)Cache Line偽共享問(wèn)題,造成大量的Cache miss,進(jìn)而導(dǎo)致程序性能下降。

第二個(gè)程序中,字段a和b中間加了一個(gè)64字節(jié)的數(shù)組,這樣就保證了這兩個(gè)字段處在不同的Cache Line上。如此一來(lái),兩個(gè)線程即便同時(shí)修改這兩個(gè)字段,兩個(gè)cache line也互不影響,cache命中率很高,程序性能會(huì)大幅提升。

結(jié)語(yǔ)

除了上述的兩個(gè)案例之外,在系統(tǒng)中CPU Cache對(duì)程序性能的影響隨處可見(jiàn)。

尤其在操作系統(tǒng)內(nèi)核關(guān)鍵代碼中,對(duì)CPU Cache更是要特別注意。Linux 內(nèi)核在進(jìn)行進(jìn)程調(diào)度和負(fù)載均衡時(shí),CPU Cache也是重點(diǎn)考量的因素之一,我在其它文章中對(duì)其進(jìn)行了介紹,有興趣的童鞋不妨去看下。

此外,循環(huán)展開(kāi)也是一種很有用的優(yōu)化方式,可以去看一下我的另外一篇文章:

《精通C語(yǔ)言?短短20行經(jīng)典C語(yǔ)言代碼很多人看不明白,你來(lái)試一下吧》

對(duì)程序運(yùn)行背后所隱藏編譯、鏈接、加載等系統(tǒng)技術(shù)感興趣的童鞋,歡迎看一下我正在連載的系列專題文章:

《你真的理解"Hello world"嗎? 從編譯鏈接到OS內(nèi)核系列專題》

覺(jué)得有用的話,點(diǎn)個(gè)贊唄,把知識(shí)分享給更多同道中人!謝謝!

也歡迎留言討論,右上角關(guān)注:-)

總結(jié)

以上是生活随笔為你收集整理的c语言一行代码太长,C语言修改一行代码,运行效率居然提升数倍,这个技巧你知道吗...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。