Data alignment(資料、記憶體對齊)漫談
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

特此宣告:轉載需要說明且附上本人連結!

  • 對於資料對齊,很多人都是知其一,而不知其二。比如他聽說過記憶體對齊和其大概的作用,但是卻不知道cache對齊以及對齊到底有什麼作用,更不瞭解怎麼能更好的對結構進行記憶體佈局以提高效能,在本文,你會得到解答。
  • 以下討論的概念性的東西應該都是適用於所有系統的,但是實際操作都是linux系統做的。
  • 討論基於單執行緒處理,目的是為了簡化討論,簡化測試,但並不影響對理論的驗證。
  • 最後附上驗證原始碼以及其解釋。

背景之個人理解

本節內容都是個人的理解,如果有不正確的地方,歡迎討論。
  說記憶體對齊之前,先說說硬碟對資料的組織方式。我們知道,檔案系統管理資料是按照塊來管理的,假如說一個4k對齊的檔案系統,一個inode節點對應一個4k大小的塊,當你寫入一個size為1k的檔案的時候,這個檔案會得到一個inode,對應的寫入其4k塊中,之後這個塊不會儲存其他檔案的內容了,新的檔案會有新的inode。所以對於一個4k大小格式化的檔案系統,如果在其上儲存的所有檔案都是1K這麼大,那麼硬碟會極大地浪費儲存空間,每個4k塊都僅僅儲存了1K的資料,剩餘3k都閒置了。ext4預設是4k一個塊,對應8個硬碟扇區。為了簡化討論,我們以檔案系統的1塊對應硬碟一個扇區的系統來討論。假如我們寫入1byte,那麼事實上硬碟的寫入了這個1byte也會佔用寫入的那個扇區,這個扇區不會再寫入其他資料了,閒置了511bytes的空間。那麼我們不禁會問,我們為什麼要這麼做?為什麼會有扇區這個概念?多浪費空間啊!硬碟創始之初為什麼要這麼設計呢?
  我的猜想是這樣:假設硬碟的碟片容量是100萬bytes,那麼一個機械硬碟一個探頭,按照1byte的後設資料粒度定位一個位置,是得有多麼的慢。。。假如我們按照sector劃分,按照512的粒度去定位,是不是就快了很多?儘管對於小於512bytes的檔案會有空間浪費,但是這些浪費是很值得的,極大地提高了定位速度。本來cpu就快,記憶體也很快,IO如果以1byte的粒度定位,那程式沒法做IO了。另外,我們一個通用檔案系統不可能都儲存這麼小的檔案。對於超過512bytes的檔案來說,最多浪費了佔用的最後一個扇區的部分空間而已。進而我們可以想象為什麼ext4會設計成預設4k一個邏輯塊對應了8個扇區了,都是為了定位速度,當然也有後設資料方面的考慮,但是都是為了速度。
  通過上面的猜想,我們推想作業系統對記憶體的組織方式。我們都知道記憶體是按照頁來管理的,為什麼要這樣?就是為了定位速度(當然此時也是有後設資料量的考慮),要知道記憶體遠比cpu慢。

記憶體對齊

  現在可以說說cpu是如何從記憶體當中取資料了(可以參考Memory access granularity)。
  程式設計師眼中的記憶體:
  這裡寫圖片描述
  cpu眼中的記憶體:
  這裡寫圖片描述
  這裡我懶了,盜用上述連結的圖,對於64位的機器,一般cpu取儲存記憶體資料的粒度是64bits除以8為8bytes。咱們以64位的討論。也就是說cpu取一次資料不是1byte,而是8bytes,它不會忙活一次就拿那麼點,而是能拿多少拿多少,免得跑那麼多次。另一個側面看的話,也會提升訪問效率。
  現在又有一個問題了,那就是cpu它從什麼位置開始取?隨便嗎?想想上面討論的sector的來歷,如果cpu一個byte一個byte的計算位置,慢不慢?事實上cpu的尋找方式是8個bytes 8個bytes的找,這下計算到哪找快多了吧?讀/寫總是從cpu資料訪問粒度的倍數的地址開始的:0、8、16等等(通過wikipedia的Data Structure Alignment詞條以及多篇中英文文章的閱讀推斷出來的,想深究的可以深究下告訴我)。
  有了上面的基礎去討論記憶體對齊對效能的影響還不夠,還需要了解什麼是記憶體對齊。

記憶體對齊:基本型別的記憶體起始地址是其大小的整數倍;複合型別的記憶體地址是其最大成員的大小的整數倍(對於複合型別如struct的記憶體padding自動調整到按照最大成員來補齊以方便複合型別記憶體對齊的知識比較簡單,這裡就不介紹了。要注意這叫做記憶體補齊,是為了記憶體對齊做的)。

  那麼現在就來看看記憶體對不對齊對cpu訪問的效能有什麼影響(基於cpu對記憶體的訪問)。
– case1 記憶體訪問粒度為1個位元組(cpu眼中的記憶體模型等價於程式設計師眼中的記憶體模型):
這裡寫圖片描述
Result:讀取4個位元組,兩者都需要進行4次記憶體訪問操作。在粒度1的情況下不需要考慮記憶體對齊。
– case2 記憶體訪問粒度為2個位元組:
這裡寫圖片描述
Result:讀取4個位元組,左邊的(記憶體對齊地址)只需要進行2次記憶體訪問操作,右邊的需要進行3次記憶體訪問操作 附加操作(見下文)。記憶體對齊地址取勝!
– case3 記憶體訪問粒度為4個位元組:
這裡寫圖片描述
Result:讀取4個位元組,左邊的只需要進行1次記憶體訪問操作,右邊的需要進行2次記憶體訪問操作 附加操作。記憶體對齊地址再次取勝!

簡單討論:
  記憶體對齊地址vs沒有記憶體對齊的地址,在三種不同的記憶體訪問粒度下,取得了2勝一平的完勝戰績。對於32位的機器,實際的記憶體訪問粒度是4個位元組,原因如下:
每一次記憶體訪問操作需要一個常量開銷;
在資料量一定的情況下,減少記憶體訪問操作,能提高程式執行效能;
增大記憶體訪問粒度(當然不超過資料匯流排的頻寬),能減少記憶體訪問操作(從上面的例項就能夠看出來);
一句話,記憶體對齊確實可以提高程式效能。

   cpu如何處理沒有記憶體對齊的資料訪問?
     1. 讀取資料所在的第一塊記憶體空間(0-1),移除多餘位元組(0)
     2. 讀取資料所在的第二塊記憶體空間(2-3)
     3. 讀取資料所在的第三塊記憶體空間(4-5),移除多餘位元組(5)
     4. 把三塊資料拼接起來(1-4),放入暫存器中。

  如果cpu能這麼來處理,也只不過是影響了我們程式的執行效能,至少還是能執行的,但有的cpu並沒這麼“勤快”,遇到沒有記憶體對齊的資料訪問,它會直接丟擲一個異常。作業系統可能會響應這個異常,用軟體的方式來處理,效能只會更差,或者程式直接崩潰掉。 記憶體對齊的程式碼具有更高的可移植性。

cpu cache

  看完上述內容,是不是覺得寫程式碼一定要注意記憶體對齊啊,好處多多。但是很遺憾,需要了解的知識還有很多,先從cache入手。
  現代cpu都是有cache(如下圖)系統的,cpu訪問資料並不是直接訪問記憶體中的資料,而是先訪問cache,如先L1 cache,後L2,L3 cache,都不命中才會訪問記憶體。那麼問題來了,cache究竟是怎麼組織的?
  這裡寫圖片描述
  我們拋開L2、L3,討論他們會使問題複雜化,我們只考慮L1 cache就能理解問題了,下面只說L1 cache。
  可以通過dmidecode命令找到Cache Infomation項或者lstopo命令看到cache資訊。現在問題又來了,這麼大個cache空間,是組織在一起的?當然不是,這“耦合度”多高。這裡有一個cache line的概念,是快取管理的最基本單元,cache line的大小可以通過命令cat /proc/cpuinfo來檢視,其中的cache_alignment就是cache line的大小,單位是byte。拿cache line為64來說,如果L1 cache是32k,那麼每個core就會擁有32 * 1024 / 64這麼多個cache line可以cache資料/指令。cpu讀取記憶體資料的時候有個特性,如果沒有,那就是每次即便你只讀取1byte,它也會從記憶體的cache line對齊的位置的第一個包含了資料部的位置開始載入一個cache line這麼多的資料到一個cache line之中。下次如果cache失效之前還用資料,就從cache裡找包含要訪問的資料的cache line,直接拿來用(當然這裡包含cache一致性的問題,後面會簡單說下)。那麼問題來了,訪問cache line有沒有上面說的對齊的問題?也有,但是以cache的速度來說,你很難感知到,可以理解為沒差別。如下圖。
  這裡寫圖片描述

  現在我們應該對cpu如何從無cache的情況訪問資料有一個認識了:

  1. 以cache line(64的倍數的地址作為起始地址)對齊的方式訪問記憶體,載入一個cache line大小的資料(如果不是cache line對齊,可能載入2個cache line)
  2. 以cpu粒度對齊訪問cache line取得資料(如果不是cache line對齊並且大小可以被cache line整除,可能跨2個cache line)

記憶體對齊對效能的影響

  現在是時候完整的算上cache來討論記憶體對齊對效能的影響了。

  1. 對於第一步從記憶體載入資料到cache的過程,由於記憶體載入資料到cache是以cache line對齊的方式,時間效能其實都用在了cache line的載入上。問題是如果你的結構跨了cache line,那麼載入它就會載入倆cache line。
  2. 對於第二步從cache line到cpu暫存器的過程,我這裡認為對不對齊對時間效能的影響可以忽略不計。因為L1 cache實在是太快了,你會無感覺的。
  3. 對於原子操作,不對齊的話非常傷效能。大步長的迴圈不對齊非常傷效能。
  4. 可以預見多執行緒程式設計的話,一邊讀一邊寫什麼的,不對齊可能會非常傷效能。因為cache一致性機制就需要保證核間cache包括主存的一致性了。

  按照上述,那也就是說資料對不對齊除了某些場景下效能影響較大,其他也無所謂了?也不是,類似於英特爾的SSE指令對記憶體對齊有著嚴格的要求,類似於某些指令架構不支援非對齊訪問,類似於有的架構的原子操作要求必須對齊等。

  你可能注意到上面談到的都是讀,寫的話大同小異,我只想了解該不該對齊,有沒有用,不想研究那麼細緻。

cache一致性問題

  cpu的每一個core都有自己的L1 cache,cache之間怎麼協同的?併發程式設計時這非常重要。通過學習,我從語義上總結如下(也經過了實測):

  1. 對於讀,每個核心都有自己的cache,如果cache有,編譯器會優化(不一定非得迴圈才優化)讀cache而忘記記憶體,除非你加上了記憶體屏障或有記憶體屏障作用的指令,比如互斥量,原子操作,慢系統呼叫等等。
  2. 對於寫,先會寫到cache,在強保證順序一致性的記憶體模型架構中你可以認為語義上它寫到了記憶體,但現實是即便像x86這樣的強記憶體模型的架構也不是語義上百分百保證cache一致性的,所以一個執行緒寫,另一個執行緒想感知到,必須通過具有記憶體屏障作用的指令(比如lock、memory barrier、atomic operation等)來保證。

  對於快取一致性的原理講解,當時瞭解了上述語義就沒去記憶細節。有興趣的看參考連結或自行google [cache coherence]。這裡可能會疑惑為什麼要在說記憶體對齊對效能的影響的時候提cache一致性,因為理解原子操作的語義時需要用到。

原子操作

原子操作是時候登場了。原子操作是什麼就不介紹了,自行搜尋。我只想說說我理解到的語義上的原子操作和匯流排鎖有什麼區別。

  1. 匯流排鎖: 顧名思義,會鎖住匯流排,其他核心只能等待,自行體味效能。
  2. 原子操作:只鎖住所在的cache line,寫的時候不一定就寫到記憶體,至於一致性,通過cache coherence保證,自行體味效能。另外,如果對跨cache line的資料做原子操作,有的架構可能不支援,有的架構可能會退化成匯流排鎖,其他即便不退化為匯流排鎖,效能也會很差。

  這裡可能會疑惑為什麼要在說記憶體對齊對效能的影響的時候提原子操作,因為我的例子中會用到,為了方便解釋例子,這裡必須說下原子操作。

怎麼做比較好

  綜上述討論,我認為這麼做比較好:

  1. 結構設計的時候一定要考慮cache line的整除性。也就是說一個結構體,大小儘可能被cache line整除,可以通過新增padding很容易做到,這不是編譯器能做到的。這樣做的好處的顯而易見,多個相同結構連續上後不會出現誇cache line的時候,提高了效率;cache line邊界的它的某個物件失效時也不會一下子失效倆cache line。這叫做cache友好,這也是記憶體佈局效能優化最最重要的地方,據說核心程式碼隨處可見。另外,也不會出現一個cache line內有兩個不相關的資料,一個失效了不會導致另一個也隨著cache line的失效而失效。
  2. 相關聯的資料儘量放到一個cache line中。這樣的話載入一個資料的時候,另一個就隨著cache line的載入被動的載入進來了。
  3. 記憶體池上分配時要按照cache line大小的倍數來對齊,nginx就是這麼做。
  4. 記憶體池上的物件分配時要按照至少cpu粒度大小的倍數對齊,最好是64位機器按16bytes對齊,nginx就是這麼做,另外malloc預設就是。

對於第1點,給個例子如下:

struct Test {
char ch1;
int  i1;
...
char padding[16]; /*純粹為了湊湊使得Test大小等於cache line*/
}

  另外,其實malloc等等各種系統記憶體分配api得到的記憶體空間的首地址都會是cpu粒度對齊的,一般情況下你不需要操心。據我所知,32位系統malloc按8對齊,64位按16對齊。如果你有特殊需求需要自定義對齊,可以通過posix_memalign這個posix標準的api自定義。比如你要按照cache line對齊時,比如你要使用SSE等特殊場景需要特殊對齊時,比如你要按照記憶體也4k對齊時等等。

測試程式碼

懶了,用了一點cpp11語法,很容易改成cpp0x的。

程式碼:

#include <iostream>
#include <sys/time.h>
#include <cstring>
#include <vector>
// 測試組的粒度
#define TEST_GROUPS   128
// 測試個數
long long len = TEST_GROUPS * 1024 * 1024L;
// 是否對齊:開啟為不對齊
//#define NON_ALIGN
// 是否測試原子行為
//#define ATOMIC
// 以下3個巨集為測試函式開關
#define BIG_PAGE_TEST
//#define SMALL_OBJ_TEST
//#define SMALL_OBJ_4K_TEST
struct A {
char      ch[6];      /* 6 bytes */
long long padding[7]; /* 56 bytes */
long long ll;         /* 如果不對齊,則從62開始,非cache line友好。 */
#ifndef NON_ALIGN
};
#else
} __attribute__((packed));
#endif
struct LinkNode {
LinkNode() {
void *pa = nullptr;
if (int ret = posix_memalign(&pa, 64, sizeof(A))) {
std::cerr << "posix_memalign err = " << strerror(ret) << std::endl;
exit(ret);
}
a = (A*)pa;
}
A        *a;
LinkNode *next;
};
__time_t big_page_test(int step = 64 * 2);
__time_t small_obj_test();
__time_t small_obj_4k_test();
void pretty_print(std::string func_name, __time_t msecs);
int main() {
std::string func_name;
__time_t msecs;
#ifdef BIG_PAGE_TEST
func_name = "big_page_test";
msecs = big_page_test();
#endif
#ifdef SMALL_OBJ_TEST
A a;
A *pa = &a;
std::cout << "pa = "         << pa         << std::endl;
std::cout << "pa.ll = "      << &pa->ll    << std::endl;
std::cout << "alignof(A) = " << alignof(A) << std::endl;
std::cout << "sizeof(A) = "  << sizeof(A)  << std::endl;
func_name = "small_obj_test";
msecs = small_obj_test();
#endif
#ifdef SMALL_OBJ_4K_TEST
A a;
A *pa = &a;
std::cout << "pa = "         << pa         << std::endl;
std::cout << "pa.ll = "      << &pa->ll    << std::endl;
std::cout << "alignof(A) = " << alignof(A) << std::endl;
std::cout << "sizeof(A) = "  << sizeof(A)  << std::endl;
func_name = "small_obj_4k_test";
msecs = small_obj_4k_test();
#endif
pretty_print(func_name, msecs);
return 0;
}
/**
* 幾點說明:
*   0. 以下均為單執行緒下的測試,如果換了多執行緒,可能差距會進一步拉大。
*   1. 以下測試都使用了儘可能大的記憶體,盡力弱化L2 L3 cache的影響。
*   2. big_page_test和small_obj_test都沒有排除硬體預取的影響。
*      small_obj_4k_test會排除硬體預取的影響(預取不會超過一個page cache,
*      page cache大小是4k)。
*   3. 每一個case都測試了5次取得均值(更好的方式應該是測試更多次並去掉最優和最劣的結果)。
*   4. 每一個測試的結對值結果都是" [非對齊ms] vs [對齊ms] -> [非對齊ms] : [對齊ms] "。
*   5. 測試機配置:
*                - L1 32KB,L2 256KB,L3 8196KB,cache line 64bytes
*                - memory 16GB
*                - cpu cores 4(超執行緒8執行緒),3.50GHz
*                - kernel 3.19.0-84-generic
*/
/**
* 測試大的記憶體頁[TEST_GROUPS == 128]。
* 注:
*   1. 預設僅測試跨cache line邊界的(步長step為兩個cache line大小,我機器是64,讀者自行掌握)。
*   2. 讀者可以自行把步長調整到8,即每次讀取一個機器字(相信應該都是64位的吧)。
*
* 現象及解釋:
*   [普通讀操作]
*     case 1: 步長為128
*       現象: 22 vs 13 -> 1.69。對不對齊效能比值差距很大,
*             但是這種測試壓力下絕對值差距不是很明顯。
*       解釋: 因為首次讀取cache中沒有資料,需要載入cache line,
*             對齊比不對齊少載入一個cache line(假如不考慮L1預取)。
*     case 2: 步長為8
*       現象: 60 vs 60 -> 1。對不對齊無感。
*       解釋: 對於long long這種8位元組的小型別,cache line內的資料居多,跨cache line的很少。
*             cpu大多數操作都命中cache line,而這麼大資料量下,
*             case 1中的cache line邊緣訪問佔比較小了,效能損耗相對很難感知。
*   [原子操作]
*     case 1: 步長為128
*       現象: 5721 vs 109 -> 52.97。對不對齊效能差別非常非常大。
*       解釋: 考慮到讀寫、cache一致性以及原子操作的“實時”性,另外這種不對齊的原子行為很有可能用了匯流排鎖,差別比[普通讀操作]大非常多。
*     case 2: 步長為8
*       現象: 10500 vs 700 -> 15。對不對齊效能差據非常大!
*       解釋: 同case 1。但由於步長小,有了比較多的cache命中,比值趨向柔和。
* @return
*/
__time_t big_page_test(int step) {
void *p = nullptr;
size_t area_size = sizeof(long long) * (len   1);
if (int ret = posix_memalign(&p, 64, area_size)) {
std::cerr << "posix_memalign err = " << strerror(ret) << std::endl;
return 0;
}
auto pc = (volatile char*)(p);
// 此case會導致邊界訪問long long跨cache line
#ifdef NON_ALIGN
pc  = 62;
#else
pc  = 64;
#endif
volatile unsigned long long tmp;
struct timeval t_val_start;
gettimeofday(&t_val_start, NULL);
for (long long i = 0; i < area_size / step - 1;   i) {
#ifdef ATOMIC
__sync_fetch_and_add((unsigned long long*)pc, i);
#else
tmp = *((unsigned long long*)pc);
#endif
pc  = step;
}
struct timeval t_val_end;
gettimeofday(&t_val_end, NULL);
auto secs = t_val_end.tv_sec - t_val_start.tv_sec;
auto usecs = t_val_end.tv_usec - t_val_start.tv_usec;
auto msecs = secs * 1000   usecs / 1000;
return msecs;
}
/**
* 測試小的連結串列連線的記憶體物件[TEST_GROUPS == 32]。
* 連結串列及其中的結構都按照64 cache line大小對齊,小步長。
* 現象及解釋:
*   [普通讀操作]
*       現象: 835 vs 1275 -> 0.66。對齊反而比不對齊慢了很多。
*       解釋: 按照我的結構體大小來看,對不對齊都會佔兩個cache line,
*             不對齊也不會省cache,為什麼非原子操作不對齊反而會變快?
*             [!猜測!]通過記憶體地址的觀察,我發現步長都超過了100bytes,
*             結果就是對齊每200多bytes左右才會miss一次cache line,
*             而不對齊會連續miss cache line,會促進預取(偶爾一次cache miss很正常,比如使用了一個不常用的全域性變數,只有連續cache miss幾次才會可能觸發預取,關於預取參考memory prefetch)。
*             我們不要忽略了硬體預取的特性。硬體在獲取資料的時候
*             如果演算法內判斷可以預取優化,它就會預取之後可能用到的資料。
*             -> small_obj_4k_test會驗證這個猜測。
*   [原子操作]
*       現象: 27500 vs 1600 -> 17.19。差距非常大,並且和big_page_test的小步長結果很像。
*       類似於big_page_test。此時預取的效果會被原子的負載抵消掉。
* @return
*/
__time_t small_obj_test() {
LinkNode *head = new LinkNode();
head->a = nullptr;
auto cur_pre_node = head;
for (long long i = 0; i < len;   i) {
void *pln;
if (int ret = posix_memalign(&pln, 64, sizeof(LinkNode))) {
std::cerr << "posix_memalign err = " << strerror(ret) << std::endl;
return 0;
}
auto ln = new(pln)LinkNode();
cur_pre_node->next = ln;
cur_pre_node = ln;
}
volatile unsigned long long tmp;
struct timeval t_val_start;
gettimeofday(&t_val_start, NULL);
long long i;
auto cur_node = head->next;
for (i = 0; i < len;   i) {
#ifdef ATOMIC
__sync_fetch_and_add(&cur_node->a->ll, i);
#else
tmp = *((volatile unsigned long long*)(&cur_node->a->ll));
#endif
cur_node = cur_node->next;
}
struct timeval t_val_end;
gettimeofday(&t_val_end, NULL);
auto secs = t_val_end.tv_sec - t_val_start.tv_sec;
auto usecs = t_val_end.tv_usec - t_val_start.tv_usec;
auto msecs = secs * 1000   usecs / 1000;
return msecs;
}
/**
* 測試小的連結串列連線的記憶體物件[TEST_GROUPS == 32]。
* 基本思想:硬體預取最多會預取一個page cache即4k內的資料,所以加入我的步長超過了4k,
*          那麼預取就沒有效果了。
*  [普通讀操作]
*       現象: 72 vs 55 -> 1.31。對不對齊有較大差距。
*       解釋: 步長跨4k排除預取干擾,對不對齊迴歸理論且差距很明顯。
*             有的可能會問了,那為什麼big_page_test的小步長沒出現small_obj_test
*             的那種反常情況?因為big_page_test是long long型用的連續記憶體,不存在空隙,
*             對不對齊都能很好的預測預取。
* @return
*/
__time_t small_obj_4k_test() {
std::vector<LinkNode*> page_head;
page_head.reserve(512);
LinkNode *head = new LinkNode();
head->a = nullptr;
auto cur_pre_node = head;
LinkNode *last_4k_node;
for (long long i = 0; i < len;   i) {
void *pln;
if (int ret = posix_memalign(&pln, 64, sizeof(LinkNode))) {
std::cerr << "posix_memalign err = " << strerror(ret) << std::endl;
return 0;
}
auto ln = new(pln)LinkNode();
if (0 == i) {
last_4k_node = ln;
} else if ((reinterpret_cast<long>(&ln->a->ll)
- reinterpret_cast<long>(&last_4k_node->a->ll))
> 1024 * 4) {
page_head.push_back(ln);
last_4k_node = ln;
}
cur_pre_node->next = ln;
cur_pre_node = ln;
}
volatile unsigned long long tmp;
struct timeval t_val_start;
gettimeofday(&t_val_start, NULL);
for (auto p : page_head) {
tmp = *((volatile unsigned long long*)(&p->a->ll));
}
struct timeval t_val_end;
gettimeofday(&t_val_end, NULL);
auto secs = t_val_end.tv_sec - t_val_start.tv_sec;
auto usecs = t_val_end.tv_usec - t_val_start.tv_usec;
auto msecs = secs * 1000   usecs / 1000;
return msecs;
}
void pretty_print(std::string func_name, __time_t msecs) {
std::cout << func_name << " duration "
#ifdef NON_ALIGN
<< "[without align] attr "
#else
<< "[with align] attr "
#endif
#ifdef ATOMIC
<< "[with atomic] "
#else
<< "[without atomic] "
#endif
<< "is [" << msecs << "] milli seconds." << std::endl;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.6)
project(mem_align)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-O2")
set(SOURCE_FILES main.cpp)
add_executable(mem_align ${SOURCE_FILES})

趣事分享

  其實我在測試的時候,寫過第四個用例,就是對big_page_test的改造,不想貼太多程式碼就沒貼出來,並且也和主題關係不是很大。我定義了一個結構體,成員為char和long long兩個。我在測試的方法就是順序的讀(不是原子操作)long long(步長為9,就等於一個結構體一個結構體的訪問),我發現用指令packed對齊之後速度反而比不對齊快了,通過上面的講解,很容易想到為什麼。

參考連結

相關文章

程式語言 最新文章