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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

對編譯、鏈接、OS內核、系統調優等技術感興趣的童鞋,不妨右上角關注一下吧,近期會持續更新相關方面的專題文章!引言

近日,網上看到一篇文章,分析數組訪問的性能問題。文章經過一系列“有理有據”的論證之后,居然得出結論:訪問數組的任意一個元素,程序性能上沒有任何差異。

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

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

2020-9-16 21:52 上傳

真的沒有差異嗎?還是用數據說話吧!

注:為了盡可能把來龍去脈講清楚,篇幅稍長,請耐心看下去,相信你會有收獲!

實例一 多維數組交換行列訪問順序

這是演示Cache對程序性能影響的經典例子:

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

2020-9-16 21:52 上傳

array1.c 和 array2.c

兩個程序只有一行差異:

第一個程序對數組按行進行訪問第二個程序對數組按列進行訪問

測試環境

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

兩個比較關鍵的參數:

Cache size:22MB

Cache line :64 字節

具體參數如下圖所示:

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

2020-9-16 21:52 上傳

cpuinfo

編譯

使用GCC編譯,使用默認優化級別。如下圖所示:

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

2020-9-16 21:52 上傳

編譯

運行

用time命令測量一下兩個程序性能差異。運行結果如下圖:

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

2020-9-16 21:52 上傳

運行結果

結果

從測試結果看,第一個程序運行花費0.265秒,第二個花費1.998秒。第二個程序消耗的時間居然是第一個程序的7.5倍!

這是為什么呢?當然是因為Cache!后面進行解釋。

我們再來看一個多線程的例子。

實例二 多線程訪問數據結構的不同字段

這個例子中,我們定義個全局結構體變量 data,然后創建兩個線程,分別訪問data的兩個字段data.a和data.b。

兩個程序的線程實現代碼如下:

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

2020-9-16 21:52 上傳

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

main()函數很簡單,只是創建兩個線程:

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

2020-9-16 21:52 上傳

main()函數

兩個例子中唯一的不同之處是:

第一個程序中,字段a和字段b是緊挨著的第二個程序中,字段a和字段b中間有一個大小為64個字節的字符數組。

測試環境和第一個例子一樣。

編譯

創建線程使用到了pthread庫,因此編譯時需要加上 -lpthread。

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

2020-9-16 21:52 上傳

編譯

運行

同樣使用time命令測量兩個程序的執行時間,結果如下圖所示:

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

2020-9-16 21:52 上傳

執行結果

結果

從測試結果看,第一個程序消耗的時間是第二個程序的3倍!

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

在解釋具體原因之前,先簡單介紹一些關于計算機存儲的基礎知識。

存儲金字塔

“存儲金字塔”這個詞,大家應該都不陌生吧,它指的是現代計算機系統的分級存儲器體系結構。

簡單來說,就是離CPU越近的存儲器訪問速度越快,但是生產成本越高,因此容量就越小。而離CPU越遠的存儲器訪問越慢,但是成本越低,因此容量就越大。

它看起來就像一個金字塔一樣,這就是“存儲金字塔”這個詞的由來。

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

2020-9-16 21:52 上傳

存儲金字塔

最頂端,離CPU最近的是寄存器,它的訪問速度最快,容量也最小,現代的CPU一般最多只有幾十個內置寄存器。

最底端,離CPU最遠的是網絡存儲設備,既然要通過網絡進行訪問,可想而知,它的速度肯定是最慢的,但是容量卻幾乎不受限制。尤其隨著近年來云計算的蓬勃發展,我們的很多數據都是存儲在云端,分布在世界各地。

我們可以簡單的認為,高一級的存儲器是低一級存儲器的緩存。也就是把低一級層存儲器中最經常被訪問的數據,存放在高一層的存儲器中,因為它離CPU更近,訪問速度更快。CPU每次訪問數據時,首先在高一級存儲器中查找,如果數據存在,就可以直接訪問,否則需要到低一級的存儲器中去查找。

這種金字塔式的存儲結構之所以能夠很好的工作,得益于計算機程序的局部性原理。

局部性原理

一個設計優良的計算機程序通常具有很好的局部性,包括時間局部性和空間局部性。

時間局部性:如果一個數據被訪問過一次,那么很有可能它會在很短的時間內再次被訪問??臻g局部性:如果一個數據被訪問了,那么很有可能位于這個數據附近的其它數據也會很快被訪問到。

一般來說,具有良好局部性的程序會比局部性較差的程序運行的更快,程序性能更好。

數組就是一種把局部性原理利用到極致的數據結構,后面會詳細說明。

高速緩存存儲器 - Cache

我們知道,程序在執行之前,必須要先加載到內存(DRAM主存儲器)中,然后數據和指令才能被CPU訪問。

但是,由于CPU和內存訪問速度之間存在著幾個數量級的巨大的差距,如果CPU每次都要從內存中去讀取數據的,就會導致大量的計算資源閑置,這對現代CPU是不可接受的。

為了解決這個問題,在CPU和內存之間設計了高速緩存存儲器,即Cache。

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

2020-9-16 21:52 上傳

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

這樣一來,CPU在讀取數據時,就會先逐級在Cache中查找,如果找到就直接從Cache讀取,找不到則從內存中讀取。

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

Cache miss的時候,CPU就不得不直接從內存中訪問數據,會面臨嚴重的performance懲罰。因此Cache miss率比較高的程序,performance會比較差。

Cache Line

Cache Line 可以理解為是 Cache和內存之間進行數據傳輸的最小單位。

很多現代CPU的Cache line大小是64個字節,我所用的測試環境Cache Line大小就是64個字節。也就是說每次數據在Cache和內存之間傳輸,并不是一個字節一個字節進行傳輸的,而是以Cache Line為單位進行傳輸的。

比如下面這個數組:

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

假設數組a的的起始地址在內存中是Cache Line對齊的(簡單理解就是數組a的起始地址能被64整除),假如我們執行下面的代碼:

int k = a[0][0];

在x86機器上,int是4個字節,在把a[0][0]賦值給k時,會把一個Cache line大小的數據從內存加載到Cache中,64/4 = 16個int,也就是說整個數組都被加載進了Cache中。如果接下來一條指令繼續訪問數組a的某個元素的話,就可以直接訪問Cache的內容。

Cache 一致性

在多CPU的系統中,每個CPU都有自己的本地Cache。因此,同一個地址的數據,有可能在多個CPU的本地 Cache 里存在多份拷貝。

為了保證程序執行的正確性,就必須保證同一個變量,每個CPU看到的值都是一樣的。也就是說,必須要保證每個CPU的本地Cache中能夠如實反映內存中的真實數據。

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

2020-9-16 21:52 上傳

假設一個變量在CPU0和CPU1的本地Cache中都有一份拷貝,當CPU0修改了這個變量時,就必須以某種方式通知CPU1,以便CPU1能夠及時更新自己本地Cache中的拷貝,這樣才能在兩個CPU之間保持數據的同步。

注:現代CPU為了保證Cache一致性,都實現了非常復雜的Cache一致性協議,如MESI等。篇幅有限,這里不再贅述,以后會更新專門的文章進行講解,有興趣的童鞋不妨關注一下。

需要注意的是,CPU之間的這種同步,是有很大開銷的。這其實也是案例二的主要原因,后面會進行說明。

了解存儲金字塔和Cache的背景知識后,現在我們分析一下前面的兩個案例。

案例一原因分析

案例一中,兩個程序都是對一個同樣大小的數組逐個元素進行賦值。唯一的區別是:

第一個程序對數組按行進行賦值第二個程序對數組按列進行賦值

為什么程序運行效率差距竟有7倍之大呢?

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

2020-9-16 21:52 上傳

我們知道,數組元素存儲在地址連續的內存中,多維數組在內存中是按行進行存儲的。

第一個程序按行訪問某個元素時,該元素附近的一個Cache Line大小的元素都會被加載到Cache中,這樣一來,在訪問緊挨著的下一個元素時,就可以直接訪問Cache中的數據,不需要再從內存中加載數據。也就是說,對數組按行進行訪問時,具有更好的空間局部性, Cache命中率更高。

第二個程序按列訪問某個元素時,雖然該元素附近的一個Cache Line大小的元素也會被加載進Cache中,但是程序接下來要訪問的數據卻不是緊挨著的那個元素,因此很有可能會再次產生Cache miss,而不得不從內存中加載數據。

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

2020-9-16 21:52 上傳

而且,雖然Cache中會盡量保存最近訪問過的數據,但由于Cache大小有限,當Cache被占滿時,就不得不把一些數據給替換掉。這也是空間局部性差的程序更容易產生Cache miss的重要原因之一。

案例二原因分析

案例二中,兩個程序都有兩個線程,每個線程分別訪問一個結構體變量的不同字段。唯一的區別是,

第一個程序中,字段a和字段b是緊挨著的。第二個程序中,字段a和字段b中間有一個大小為64個字節的字符數組。

這其實涉及到Cache Line的偽共享(false sharing)問題。

Cache Line偽共享

所謂Cache Line 偽共享,是由于運行在不同CPU上的不同線程,同時修改處在同一個Cache Line上的數據引起的。

雖然在每個CPU看來,各自修改的是不同的變量,但是由于這些變量在內存中彼此緊挨著的,因此它們處于同一個Cache Line上。一個CPU修改這個Cache Line之后,為了保證Cache數據的一致性,必然導致另一個CPU的本地Cache的無效,因而觸發Cache miss,然后從內存中重新加載變量被修改后的值。

多個線程頻繁的修改處于同一個Cache Line的數據,會導致大量的Cache miss,因而造成程序性能大幅下降。

原因分析

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

2020-9-16 21:52 上傳

第一個程序中,字段a和字段b處于同一個Cache Line上,當兩個線程同時修改這兩個字段時,會觸發Cache Line偽共享問題,造成大量的Cache miss,進而導致程序性能下降。

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

結語

除了上述的兩個案例之外,在系統中CPU Cache對程序性能的影響隨處可見。

尤其在操作系統內核關鍵代碼中,對CPU Cache更是要特別注意。Linux 內核在進行進程調度和負載均衡時,CPU Cache也是重點考量的因素之一,我在其它文章中對其進行了介紹,有興趣的童鞋不妨去看下。

此外,循環展開也是一種很有用的優化方式,可以去看一下我的另外一篇文章:

《精通C語言?短短20行經典C語言代碼很多人看不明白,你來試一下吧》

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

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

覺得有用的話,點個贊唄,把知識分享給更多同道中人!謝謝!

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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。