C++ Low level performance optimize

C++ Low level performance optimize程序員

 

1.  May I have 1 bit ?算法

下面兩段代碼,哪個佔用空間更少,那個速度更快?思考10秒再繼續往下看:)性能優化

//v1
struct BitBool
{
         bool b0 :1;
         bool b1 :1;
         bool b2 :1;
}
BitBool bb;
bb.b1 = true;

 //v2
struct NormalBool
{
         bool b0;
         bool b1;
         bool b2;    
}

NormalBool nb;
nb.b1 = true;

 

第一種一般被認爲是優化的版本,甚至UE3裏都有不少相似代碼,但實際上卻在兩方面都不佔優,why?緣由是現代大部分cpu都沒有指令能直接訪問1bit數據,而多線程

bb.b1 = truedom

至關於函數

bb.b1 |= (1<<xxxx); 相應彙編代碼可能爲(實際彙編根據編譯器可能會不一樣):工具

shl         cl,2 
xor         cl,al 
and         cl,4 
xor         al,cl性能

測試

nb.b1 = true;只須要
mov BYTE PTR [rcx+2], dl優化

        當考慮空間優化時通常只考慮到了數據佔用空間,而沒有考慮代碼所佔用的內存。所以雖然sizeof(BitBool)==1==8bit (默認8bit對齊的系統),但訪問b1的指令所佔空間卻須要8~11byte! 考慮到訪問成員的代碼一般會成爲內聯函數,所以BitBool所佔空間爲 1 + 11 * n ,n爲代碼中須要訪問數據的次數! 而NormalBool雖然須要3byte,但其訪問代碼只須要3byte機器碼,所佔空間爲3 + 3 * n。所以不管在性能仍是空間性,NormalBool均更好!

 

 2  Cache missing is killer!!!

     把一個隨機數列依次插入list和vector,保持兩個新數列從小到大排序:7,5,2,7,9,3  ===>   2,3,5,7,7,9 ,下面是代碼,對於不一樣的數據量n,哪種方法更好?

std::list<int> myList;
std::vector<int> myVec;

//create a random number array
const int size = 50000;
std::array<int, size> myArr;
for(int i = 0; i < size; i++)
{
       myArr[i] = rand() % 2000;
}

//pre-allocate memory
myVec.reserve(size);
myList.resize(size,65535);

//fill vector
for(int i = 0; i < size; i++)
{
    int value = myArr[i];
    auto it = myVec.begin();
    for(; it != myVec.end(); it++)
    {
        if(*it > value)
        {
            it = myVec.insert(it, value);
            break;
        }
    }
    if(it == myVec.end())
        myVec.push_back(value);
}

//fill list
for(int i = 0; i < size; i++)
{
    int value = myArr[i];
    auto it = myList.begin();
    for(; it != myList.end(); it++)
    {
        if(*it > value)
        {
            it = myList.insert(it, value);
            break;
        }
    }
    if(it == myList.end())
        myList.push_back(value);
}

 

    兩段代碼幾乎相同從算法分析的角度看,頻繁插入操做是vector的災難,但實際測試結論是不管size爲多大,vector老是比list快,而且size越大,差距越明顯,在個人機器上當size=50k時快了近10倍!!why?首先list須要佔用更多內存,其次vector老是保證元素位於連續的內存,這是最重要的!Cache missing致使的性能損失甚至比複製元素還嚴重。對現代CPU來講,運算速度已經很是快,一次cache missing就會浪費n個cpu週期,合理組織數據,讓cpu減小等待時間是現代cpu很是重要的優化手段。

    注意,上面的演示代碼只是爲了展現cache missing的重要性,並非完成這個任務的最優方法,另外實際狀況下對於複雜類型來講,隨着複製代價的提升,vector未必就能總勝出了:)。

 

 3. False Sharing(cache-line ping-ponging)

      大部分程序員都據說過cache missing,但知道false sharing的就不那麼多了。爲了討論false sharing,首先要介紹cache line. 就像CPU不能讀取1bit同樣,cpu訪問cache時一般也會多讀取一些額外數據,同時讀取的這一段數據就稱爲一條cache line。每一級cache都由n條cache line組成,對於intel i級的cpu來講cache line大小爲64byte。假設cpu須要訪問變量v時, v地址附近的數據都被讀入cache中。對於單核的世界來講,一切都很好,但對於多核心下的多線程設計來講,問題就來了,假設v被加載到了第一個核的cache中,此時另外一個核心須要訪問臨近v的變量v1怎麼辦? 若是都是隻讀操做,那麼每一個核心能夠各保存一份cache line v的副本,不會有衝突。但若是線程1須要修改v,線程2須要修改v2怎麼辦呢,顯然會致使不一樣核心的cache line狀態不一致。爲了解決這個問題,整個cache line都要被來回從新加載。好比:線程1從主內存加載cache line v,修改v,把整個cache line回寫到主內存,線程2再重複這個過程修改v1,這種狀況就稱爲false sharing,顯然若是在並行運行的核心代碼中出現這種的狀況,性能是很是糟糕的,而這樣的代碼一般又不太容易發現

下面是wiki上false sharing的一個例子:

struct foo 
{
    int x;
    int y; 
};
 
static struct foo f;
 
int sum_a(void)
{
    int s = 0;
    for (int i = 0; i < 1000000; ++i)
        s += f.x;
    return s;
}
 
void inc_b(void)
{
    int i;
    for (i = 0; i < 1000000; ++i)
        ++f.y;
}

     假設sum_a和inc_b兩個函數同事運行在不一樣核心的不一樣線程上,f的全部成員都在同一條cache line,inc_b在不聽修改內存中的值,所以致使false sharing。

 

  在作性能優化前,必定要先profile,profile,profile!!!不少狀況下,問題所在的位置和程序員所預期的都不同,盲目修改代碼甚至有可能下降程序性能!!!

ps:最悲劇的狀況就是沒有任何可靠的profile工具,還必須作性能優化,我目前的狀況就是這樣,怎一個慘字了得....

 

more refereence:
Modern C++: What You Need to Know 
Native Code Performance on Modern CPUs: A Changing Landscape
Native Code Performance and Memory: The Elephant in the CPU

相關文章
相關標籤/搜索