關於局部數組和全局數組的運行效率?

在看C++工程代碼的時候,對於該工程耗時最多的函數做了這樣一個優化。

為了簡化,就用A,B來表示。本來是這樣的:

A類的func_a方法對一個另一個貫穿整個main生存周期的類變數B的一個數組array_b進行操作,是耗時最多的操作。

優化:

A在自己的類里建了一個數組array_a,在運行func_a方法前,將array_b的內容拷貝到array_a裡面,然後用func_a方法對array_a操作。

也就是說本來是A類的方法對B類的成員變數進行操作,現在是A對自己變數進行操作了,浪費了一些空間和memcpy的時間。不過似乎真的變快了。

本來我打算將拷貝刪去看看優化的效果到底有多大,可惜發現不太容易刪掉,因為裡面還摻雜了許多其他複雜的初始化操作。我就自己寫了一個小程序試了一下這種優化,發現確實快了很多,甚至好幾倍以上。

請問這樣做的理由是什麼呢?為什麼可以變快呢?

這是我的測試代碼,B直接對A的數組進行操作,C將A的數組拷貝到自己的成員變數里再操作。

結果發現C的速度快很多。

#include &
#include &
#include &
using namespace std;
const int MAX_NUM = 500;
class A
{
public:
A(int len){
m_len = len;
m_a = new int[len];
for(int i = 0;i &< len; i++) m_a[i] = i+len; } int m_len; int* m_a; }; class B { public: B(int x){m_x = x;} int addSum(int* a,int len){ int sum = 0; for(int i = 0;i &< len; ++i){ int x = MAX_NUM; do{ sum += a[i]; sum *= a[i]; sum /= a[i]; }while(x--); } return sum; } int m_x; }; class C { public: C(int *a, int len){ m_len = len; m_b = new int[len]; memcpy(m_b,a,sizeof(int) * len); } int addSum(int* a,int len){ int sum = 0; for(int i = 0;i &< m_len; ++i){ int x = MAX_NUM; do{ sum += m_b[i]; sum *= m_b[i]; sum /= m_b[i]; }while(x--); } return sum; } int m_len; int* m_b; }; int main() { time_t st; time_t ed; clock_t t1,t2; int x,y; A a(10000); B d(1); st = time(0); t1 = clock(); C c(a.m_a, a.m_len); for(int i = 0;i &< MAX_NUM; ++i) { x = c.addSum(a.m_a,a.m_len); y=i; } ed = time(0); t2 = clock(); printf("cnt = %d,%d,time = %f,time2 = %ld ",x,y,(double)(ed-st),(t2 -t1)); st = time(0); t1 = clock(); for(int i = 0;i &< MAX_NUM; ++i) { x = d.addSum(a.m_a,a.m_len); y=i; } ed = time(0); t2 = clock(); printf("cnt = %d,%d,time = %f,time2 = %ld ",x,y,(double)(ed-st),(t2 -t1)); return 0; }


我就自己寫了一個小程序試了一下這種優化,發現確實快了很多,甚至好幾倍以上。

貼代碼、測試方法、測試結果。


同 @北極 的回答,題主的case不具有普適性。

直接拿題主的代碼在我的本上編譯運行了一下,結果放這裡了:https://gist.github.com/rednaxelafx/afec16fefee7cdbf9189#file-command_prompt

$ clang++ -O3 arrcost.cc
$ ./a.out
cnt = 2133050968,499,time = 0.000000,time2 = 1545
cnt = 2133050968,499,time = 0.000000,time2 = 1415
$ clang++ --version
Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin13.4.0
Thread model: posix

差別不大嗯。

Clang/LLVM在-O3下生成的代碼也放在那個鏈接里了,請參考。對比前後兩個版本的代碼,LLVM在-O3下生成的代碼是完全一樣的。

那個.ll文件可以用LLVM自帶的opt工具生成出圖形化的控制流圖,看得更清楚些。

opt -dot-cfg arrcost.ll

我放了一份生成好的dot文件在https://gist.github.com/rednaxelafx/afec16fefee7cdbf9189#file-cfg-main-dot

相關傳送門:LLVM中如何獲取程序的控制流圖CFG? - 編譯器


僅僅因為把數據拷貝到類里所以速度變快了?

會不會數據處理的方式也不同,所以減少了cache miss?


題主你這個case不具備普遍性。

我拿到試了一下不同的編譯器,效果是不一樣的。

VS2008 Debug模式下,二者沒有明顯差別,後者稍快一些(我稍微改了一下輸出代碼):

G:VCarrcost&>Debugarrcost.exe
cnt = 8290,499,time = 29.670000
cnt = 8290,499,time = 29.641000

Release模式下,差距明顯:

G:VCarrcost&>Releasearrcost.exe
cnt = 8290,499,time = 0.030000
cnt = 8290,499,time = 18.160000

gcc 481 + Ubuntu,-O0結果:

cnt = 8290,499,time = 30.210829
cnt = 8290,499,time = 30.162914

-O2結果:

cnt = 8290,499,time = 18.638646
cnt = 8290,499,time = 18.532594

也就是說,只有VC的release模式才有這種效果。

其它版本的VC/GCC沒實驗。

原因么,反彙編了一下Release的那個,上兩個圖:

VS2008的優化中,把C_addSum優化到循環外了。

也就是說,對於第一個addSum來說,計算流程是這樣的:

1. 計算出C_addSum,算一次;

2. 執行循環for(int i = 0;i &< MAX_NUM; ++i),但不做任何操作,只保存y值;

3. 輸出;

顯然這麼算是更快的。

對於第二個addSum來說,計算流程是:

1. 循環;

2. 循環內做B_addSum;

3. 輸出;

顯然第一個更快,因為第一個只算一次。

VC這麼優化要說道理,也是有的,因為你輸出的x只有一次,C里的東西你只初始化了一次,在整個循環的過程中,C里的東西是不變的,你傳入的參數又沒有被使用(你傳了len但沒用),C沒有被別人引用,基於這些理由,編譯器可以認為addSum所操作的東西都是不變的,你實際上只要一次addSum的結果就夠了,所以不管你循環設置的多大,編譯器優化後始終只算一次。

對於第二個來說,循環內你引用了循環外的對象a,那麼循環內就不敢隨便優化了,誰知道a里的東西會不會改變,所以編譯器會老老實實的算上MAX_NUM次,最終輸出結果。

VC這麼做其實有點激進——我個人認為不是太妥當。


A a(1《29)

再試試


不管是由B來操作A的數組,還是A自己操作,在最後的函數裡面,數組的指針都是放在某個寄存器裡面的,唯一差別是一個是由函數參數傳入,另一個是通過this指針偏移之後獲得,但都是一次性操作不影響什麼性能。之後的重複性操作都是對該寄存器中的值加個偏移量來進行定址,沒有什麼本質區別。


你的測試代碼有問題。

在有優化的情況下,代碼不能保證addSum能執行MAX_NUM次。


你這測試代碼,憑直覺就感受到編譯器可能會做手腳了,x y的計算都有可能被優化掉循環


占坑,從demo代碼來看,兩種方式沒有本質區別,操作的數據都是堆中的一個數組。如果事實上出現了性能的巨大差異,那多半是編譯優化搞得鬼。


有可能是同時也對 A 自己原來的成員變數進行了操作,於是如果被操作的數據都在 A 裡面的話,緩存命中率較高(spatial locality 嘛)。

不然就實在無法解釋了。


可能是定址時間不一樣。a變數要獲取b的地址在獲取數組的地址。


推薦閱讀:

PLT界在近幾年中對哪些問題的研究有重大進展?
HotSpot JVM參數 -XX:+DoEscapeAnalysis 有什麼影響?

TAG:演算法 | C | 編譯原理 | 程序優化 | 數組 |