欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析內(nèi)存對齊 Data alignment: Straighten up and fly right的詳解

 更新時間:2013年05月13日 09:27:40   作者:  
對于所有直接操作內(nèi)存的程序員來說,數(shù)據(jù)對齊都是很重要的問題.數(shù)據(jù)對齊對你的程序的表現(xiàn)甚至能否正常運行都會產(chǎn)生影響

    為了速度和正確性,請對齊你的數(shù)據(jù).

    概述:對于所有直接操作內(nèi)存的程序員來說,數(shù)據(jù)對齊都是很重要的問題.數(shù)據(jù)對齊對你的程序的表現(xiàn)甚至能否正常運行都會產(chǎn)生影響.就像本文章闡述的一樣,理解了對齊的本質(zhì)還能夠解釋一些處理器的"奇怪的"行為.

 

內(nèi)存存取粒度

   程序員通常傾向于認為內(nèi)存就像一個字節(jié)數(shù)組.C及其衍生語言中,char * 用來指代"一塊內(nèi)存",甚至在JAVA中也有byte[]類型來指代物理內(nèi)存.

 


Figure 1. 程序員是如何看內(nèi)存的

 

   然而,你的處理器并不是按字節(jié)塊來存取內(nèi)存的.它一般會以雙字節(jié),四字節(jié),8字節(jié),16字節(jié)甚至32字節(jié)為單位來存取內(nèi)存.我們將上述這些存取單位稱為內(nèi)存存取粒度.

 


Figure 2. 處理器是如何看內(nèi)存的

 

   高層(語言)程序員認為的內(nèi)存形態(tài)和處理器對內(nèi)存的實際處理方式之間的差異產(chǎn)生了許多有趣的問題,本文旨在闡述這些問題.

   如果你不理解內(nèi)存對齊,你編寫的程序?qū)⒂锌赡墚a(chǎn)生下面的問題,按嚴重程度遞增:

程序運行速度變慢

應(yīng)用程序產(chǎn)生死鎖

操作系統(tǒng)崩潰

你的程序會毫無征兆的出錯,產(chǎn)生錯誤的結(jié)果(silently fail如何翻譯?)

 

內(nèi)存對齊基礎(chǔ)

   為了說明內(nèi)存對齊背后的原理,我們考察一個任務(wù),并觀察內(nèi)存存取粒度是如何對該任務(wù)產(chǎn)生影響的.這個任務(wù)很簡單:先從地址0讀取4個字節(jié)到寄存器,然后從地址1讀取4個字節(jié)到寄存器.

   首先考察內(nèi)存存取粒度為1byte的情況:

 


Figure 3. 單字節(jié)存取

 

   這迎合了那些天真的程序員的觀點:從地址0和地址1讀取4字節(jié)數(shù)據(jù)都需要相同的4次操作.現(xiàn)在再看看存取粒度為雙字節(jié)的處理器(像最初的68000處理器)的情況:

 


Figure 4. 雙字節(jié)存取

 

   從地址0讀取數(shù)據(jù),雙字節(jié)存取粒度的處理器讀內(nèi)存的次數(shù)是單字節(jié)存取粒度處理器的一半.因為每次內(nèi)存存取都會產(chǎn)生一個固定的開銷,最小化內(nèi)存存取次數(shù)將提升程序的性能.

   但從地址1讀取數(shù)據(jù)時由于地址1沒有和處理器的內(nèi)存存取邊界對齊,處理器就會做一些額外的工作.地址1這樣的地址被稱作非對齊地址.由于地址1是非對齊的,雙字節(jié)存取粒度的處理器必須再讀一次內(nèi)存才能獲取想要的4個字節(jié),這減緩了操作的速度.


   
最后我們再看一下存取粒度為4字節(jié)的處理器(68030,PowerPC® 601)的情況:


Figure 5. 四字節(jié)存取

 

   在對齊的內(nèi)存地址上,四字節(jié)存取粒度處理器可以一次性的將4個字節(jié)全部讀出;而在非對齊的內(nèi)存地址上,讀取次數(shù)將加倍.

   既然你理解了內(nèi)存對齊背后的原理,那么你就可以探索該領(lǐng)域相關(guān)的一些問題了.

懶惰的處理器

   處理器對非對齊內(nèi)存的存取有一些技巧.考慮上面的四字節(jié)存取粒度處理器從地址1讀取4字節(jié)的情況,你肯定想到了下面的解決方法:


Figure 6. 處理器如何處理非對齊內(nèi)存地址

 

   處理器先從非對齊地址讀取第一個4字節(jié)塊,剔除不想要的字節(jié),然后讀取下一個4字節(jié)塊,同樣剔除不要的數(shù)據(jù),最后留下的兩塊數(shù)據(jù)合并放入寄存器.這需要做很多工作.

   有些處理器并不情愿為你做這些工作.

   最初的68000處理器的存取粒度是雙字節(jié),沒有應(yīng)對非對齊內(nèi)存地址的電路系統(tǒng).當遇到非對齊內(nèi)存地址的存取時,它將拋出一個異常.最初的Mac OS并沒有妥善處理這個異常,它會直接要求用戶重啟機器.悲劇.

   隨后的680x0系列,68020,放寬了這個的限制,支持了非對齊內(nèi)存地址存取的相關(guān)操作.這解釋了為什么一些在68020上正常運行的舊軟件會在68000上崩潰.這也解釋了為什么當時一些老Mac編程人員會將指針初始化成奇數(shù)地址.在最初的Mac機器上如果指針在使用前沒有被重新賦值成有效地址,Mac會立即跳到調(diào)試器.通常他們通過檢查調(diào)用堆棧會找到問題所在.

   所有的處理器都使用有限的晶體管來完成工作.支持非對齊內(nèi)存地址的存取操作會消減"晶體管預(yù)算",這些晶體管原本可以用來提升其他模塊的速度或者增加新的功能.

   以速度的名義犧牲非對齊內(nèi)存存取功能的一個例子就是MIPS.為了提升速度,MIPS幾乎廢除了所有的瑣碎功能.

    PowerPC各取所長.目前所有的PowPC都硬件支持非對齊的32位整型的存取.雖然犧牲掉了一部分性能,但這些損失在逐漸減少.

   另一方面,現(xiàn)今的PowPC處理器缺少對非對齊的64-bit浮點型數(shù)據(jù)的存取的硬件支持.當被要求從非對齊內(nèi)存讀取浮點數(shù)時,PowerPC會拋出異常并讓操作系統(tǒng)來處理內(nèi)存對齊這樣的雜事.軟件解決內(nèi)存對齊要比硬件慢得多.

psting 1. 每次處理一個字節(jié)

復(fù)制代碼 代碼如下:

void Munge8( void *data, uint32_t size ){
    uint8_t *data8 = (uint8_t*)data;
    uint8_t *data8End = data8 +size;

    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}



   運行這個函數(shù)需要67364微秒,現(xiàn)在修改成每次處理2個字節(jié),這將使存取次數(shù)減半:

psting 2.每次處理2個字節(jié)

復(fù)制代碼 代碼如下:

void Munge16( void *data, uint32_t size ){
    uint16_t *data16 = (uint16_t*)data;
    uint16_t *data16End = data16 + (size>> 1); /* Divide size by 2. */
    uint8_t *data8 = (uint8_t*)data16End;
    uint8_t *data8End = data8 + (size& 0x00000001); /* Strip upper 31 bits. */

    while( data16 != data16End ){
        *data16++ = -*data16;
    }
    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}

   如果處理的內(nèi)存地址是對齊的話,上述函數(shù)處理同一個緩沖區(qū)需要48765微秒--比Munge8快38%.如果緩沖區(qū)不是對齊的,處理時間會增加到66385微秒--比對齊情況下慢了27%.下圖展示了對齊內(nèi)存和非對齊內(nèi)存之間的性能對比.

速度

   下面編寫一些測試來說明非對齊內(nèi)存對性能造成的損失.過程很簡單:從一個10MB的緩沖區(qū)中讀取,取反,并寫回數(shù)據(jù).這些測試有兩個變量:

處理緩沖區(qū)的處理粒度,單位bytes. 一開始每次處理1個字節(jié),然后2個字節(jié),4個字節(jié)和8個字節(jié).

緩沖區(qū)的對準. 用每次增加緩沖區(qū)的指針來交錯調(diào)整內(nèi)存地址,然后重新做每個測試.

   這些測試運行在800MHzPowerBook G4.為了最小化中斷引起的波動,這里取十次結(jié)果的平均值.第一個是處理粒度為單字節(jié)的情況:


psting 1. 每次處理一個字節(jié)

復(fù)制代碼 代碼如下:

void Munge8( void *data, uint32_t size ){
    uint8_t *data8 = (uint8_t*)data;
    uint8_t *data8End = data8 +size;

    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}



   運行這個函數(shù)需要67364微秒,現(xiàn)在修改成每次處理2個字節(jié),這將使存取次數(shù)減半:

psting 2.每次處理2個字節(jié)

復(fù)制代碼 代碼如下:

void Munge16( void *data, uint32_t size ){
    uint16_t *data16 = (uint16_t*)data;
    uint16_t *data16End = data16 + (size>> 1); /* Divide size by 2. */
    uint8_t *data8 = (uint8_t*)data16End;
    uint8_t *data8End = data8 + (size& 0x00000001); /* Strip upper 31 bits. */

    while( data16 != data16End ){
        *data16++ = -*data16;
    }
    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}

   如果處理的內(nèi)存地址是對齊的話,上述函數(shù)處理同一個緩沖區(qū)需要48765微秒--比Munge8快38%.如果緩沖區(qū)不是對齊的,處理時間會增加到66385微秒--比對齊情況下慢了27%.下圖展示了對齊內(nèi)存和非對齊內(nèi)存之間的性能對比.


Figure7. 單字節(jié)存取 vs.雙字節(jié)存取

 

   第一個讓人注意到的現(xiàn)象是單字節(jié)存取結(jié)果很均勻,且都很慢.第二個是雙字節(jié)存取時,每當?shù)刂肥菃螖?shù)時,變慢的27%就會出現(xiàn).

   下面加大賭注,每次處理4個字節(jié):

psting 3. 每次處理4個字節(jié)

復(fù)制代碼 代碼如下:

void Munge32( void *data, uint32_t size ){
    uint32_t *data32 = (uint32_t*)data;
    uint32_t *data32End = data32 + (size>> 2); /* Divide size by 4. */
    uint8_t *data8 = (uint8_t*)data32End;
    uint8_t *data8End = data8 + (size& 0x00000003); /* Strip upper 30 bits. */

    while( data32 != data32End ){
        *data32++ = -*data32;
    }
    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}

   對于對齊的緩沖區(qū),函數(shù)需要43043微秒;對于非對齊的緩沖區(qū),函數(shù)需要55775微秒.因此,在所測試的機器上,非對齊地址的四字節(jié)存取速度比對齊地址的雙字節(jié)存取速度要慢.

 


Figure8. 單字節(jié)vs.雙字節(jié)vs.四字節(jié)存取

 

現(xiàn)在來最恐怖的:每次處理8個字節(jié):

psting 4.每次處理8個字節(jié)

復(fù)制代碼 代碼如下:

void Munge64( void *data, uint32_t size ){
    double *data64 = (double*)data;
    double *data64End = data64 + (size>> 3); /* Divide size by 8. */
    uint8_t *data8 = (uint8_t*)data64End;
    uint8_t *data8End = data8 + (size& 0x00000007); /* Strip upper 29 bits. */

    while( data64 != data64End ){
        *data64++ = -*data64;
    }
    while( data8 != data8End ){
        *data8++ = -*data8;
    }
}

    Munge64處理對齊的緩沖區(qū)需要39085微秒--大約比對齊的Munge3210%.但是,在非對齊緩沖區(qū)上的處理時間是讓人驚訝的1841155微秒--比對齊的慢了兩個數(shù)量級,慢了足足4610%.

   怎么回事?因為我們現(xiàn)今所使用的PowerPC缺少對存取非對齊內(nèi)存的浮點數(shù)的硬件支持.對每次非對齊內(nèi)存的存取,處理器都拋出一個異常.操作系統(tǒng)獲取該異常并軟件實現(xiàn)內(nèi)存對齊.下圖顯示了非對齊內(nèi)存存取帶來的不利后果.

 


Figure 9. 多字節(jié)存取對比

 

   單字節(jié),雙字節(jié)和四字節(jié)的細節(jié)都被掩蓋了.或許去除頂部以后的圖形,如下圖,更清晰:

 


Figure 10. 多字節(jié)存取對比 #2

 

   在這些數(shù)據(jù)背后還隱藏著一個微妙的現(xiàn)象.比較8字節(jié)粒度時邊界是4的倍數(shù)的內(nèi)存的存取速度:

 


Figure10. 多字節(jié)存取對比 #3

 

   你會發(fā)現(xiàn)8字節(jié)粒度時邊界為412字節(jié)的內(nèi)存存取速度要比相同情況下的42字節(jié)粒度的慢.即使PowerPC硬件支持4字節(jié)對齊的8字節(jié)雙浮點型數(shù)據(jù)的存取,你還是要承擔額外的開銷造成的損失.誠然,這種損失絕不會像4610%那么大,但還是不能忽略的.這個實驗告訴我們:存取非對齊內(nèi)存時,大粒度的存取可能會比小粒度存取還要慢

相關(guān)文章

  • 使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管

    使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管

    這篇文章主要介紹了使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管,本文章內(nèi)容詳細,具有很好的參考價值,希望對大家有所幫助,需要的朋友可以參考下
    2023-01-01
  • Qt實現(xiàn)小功能之圓形進度條的方法詳解

    Qt實現(xiàn)小功能之圓形進度條的方法詳解

    在Qt自帶的控件中,只有垂直進度條、水平進度條兩種。在平時做頁面開發(fā)時,有些時候會用到圓形進度條,比如說:下載某個文件的下載進度。本文就來實現(xiàn)一個圓形進度條,需要的可以參考一下
    2022-10-10
  • C++ 虛函數(shù)及虛函數(shù)表詳解

    C++ 虛函數(shù)及虛函數(shù)表詳解

    這篇文章主要介紹了c++ 虛函數(shù)及虛函數(shù)表詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2021-11-11
  • Visual Studio 如何創(chuàng)建C/C++項目問題

    Visual Studio 如何創(chuàng)建C/C++項目問題

    這篇文章主要介紹了Visual Studio 如何創(chuàng)建C/C++項目問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • OPENCV批量讀取圖片實現(xiàn)方法

    OPENCV批量讀取圖片實現(xiàn)方法

    下面小編就為大家?guī)硪黄狾PENCV批量讀取圖片實現(xiàn)方法。小編覺得挺不錯的?,F(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • C++學(xué)習之智能指針中的unique_ptr與shared_ptr

    C++學(xué)習之智能指針中的unique_ptr與shared_ptr

    吃獨食的unique_ptr與樂于分享的shared_ptr是C++中常見的兩個智能指針,本文主要為大家介紹了這兩個指針的使用以及智能指針使用的原因,希望對大家有所幫助
    2023-05-05
  • c語言函數(shù)如何求兩個數(shù)的最大值

    c語言函數(shù)如何求兩個數(shù)的最大值

    這篇文章主要介紹了c語言函數(shù)如何求兩個數(shù)的最大值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • c++中關(guān)于max_element()函數(shù)解讀

    c++中關(guān)于max_element()函數(shù)解讀

    這篇文章主要介紹了c++中關(guān)于max_element()函數(shù)解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 一文詳解C++子類函數(shù)為什么不能重載父類函數(shù)

    一文詳解C++子類函數(shù)為什么不能重載父類函數(shù)

    這篇文章主要介紹了一文詳解C++子類函數(shù)為什么不能重載父類函數(shù),文章圍繞主題展開詳細的內(nèi)容戒殺,具有一定的參考價值,需要的朋友可以參考一下
    2022-09-09
  • C++實現(xiàn)航空訂票系統(tǒng)課程設(shè)計

    C++實現(xiàn)航空訂票系統(tǒng)課程設(shè)計

    這篇文章主要為大家詳細介紹了C++實現(xiàn)航空訂票系統(tǒng)課程設(shè)計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論