2016年4月22日 星期五

Compound Page, Huge Page, 和Transparent Huge Page(THP)


Compound Page:
指的是記憶體的集合,包含兩個以上的連續記憶體。它會使用meta data來記錄這些記憶體,使用的flag是 __GFP_COMP。目前有一些driver會使用,另外還提供給系統核心使用,比較著名的是 hugetlbfs 和Transparent Huge Pages (THP)。
詳情可以參考 An introduction to compound pages


Huge Page [詳細介紹]:
這是20年前就開始使用的技術 (Pentium Pro),而ARM則等到2004年後在ARMv7才支援。這項技術就是將page size從4KB加大到1MB以上。[wiki]
    1. 處理大檔案時,減少 page fault 為 1/512 倍 。
    2. 同樣大小的TLB (Translate Lookaside Buffer) 可以映射更多的記憶體。Intel 分別設立2組 TLB (Pentium Pro in PAE mode),一組專門紀錄4KB page,另一組專門紀錄2MB page。
    1. 當同一page內的資料過於分散,需要花更多的搜尋時間 
    2. page fault時,載入時間比較久
    3. 因為有兩組不同的 TLB,如果要使用這項硬體功能時,是偷懶地交由user自行決定是否要使用大page,使用的機制就是 hugetlbfs。 
    4. 不能將page swap到硬碟 !!!  (不支援) [lwn]
    5. 最好在boot階段就配置好
    6. VMA (Virtual Memory Area) 也要分成 4KB 和 2MB兩組 !!!

 hugetlbfs:
使用user space介面去配置大塊連續的memory !!! 接著要自己做mmap(),不過簡單的方式是使用libhugetlbfs的HUGETLB_MORECORE=yes變數[lwn]。
  • 壞處:
    1. 在Kernel Virtual Machine底下,如果使用動態配置,可能出錯
    2. 性能不好,所以加入許多功能,搞的像是第二個 tmpfs

替代方案:
目前有兩個方案,但2016年的會議仍無法決定誰能進入Linux Mainline

  • Transparent Huge Page (THP):
每個 process都配置2MB page !!!  但當記憶體不足時會自動分裂成4KB pages
    • 好處:
      1. 會自動將2MB page分裂成 4KB pages
      2. user space programmer不用煩惱不同pages間的差異

    • 壞處:
    1. 還在孵,在2016/04/20時,還沒有實際整合到 Linux mainline
THP的競爭對手
    • 好處:
      1.  已經被google server採用將近一年 (截至2016/04),有實戰經驗


2016年4月19日 星期二

使用man查詢 /etc/networks 檔案的定義

Linux檔案系統的命名方式常常讓人搞不清楚是做什麼用,除了上網查詢外,也可以用內建的man指令查詢

打入底下的指令
$ man 5 networks

就可以查詢到定義,其中數字5代表的是查詢檔案格式,如果還想看其他數字代表的意思,可以打底下的指令查詢
$ man man

不過不要對內建的說明抱持太大的期待,當你看完/etc/networks的定義後,可能還是要乖乖地去網路上看其他資料。


2016年1月6日 星期三

高效率C語言設計

這篇文章只著重在C語言反組譯後的效能,並不探討架構。這是個冷門知識,除了無線通訊的核心程式、高速介面的核心程式外,很少有人會在乎。探討這類的書也不多,通常也很古老,最慘的是部分守則在GCC的進步下,已經沒有用了。本篇文章就一一檢視這些守則。書本用的是"ARM System Developer's Guide - Designing and Optimizing System Software"




  • 局部變數

    • 過時守則 : 32bit CPU上,不要使用char當作區域變數,要改用unsigned int 

int checksum_v1 (int *data) {
        char i;
        int sum = 0;
        for (i=0; i<64; i++) {
                sum +=data[i];
        }   

        return sum;
}

int checksum_v2 (int *data) {
        unsigned int i;
        int sum = 0;
        for (i=0; i<64; i++) {
                sum +=data[i];
        }   

        return sum;
}


理由:
     register寬度為32bit。如果變數是char,編譯器反而要register跟 0xFF 做mask,這樣才能保證不會溢位。且unsigned可以熱

實驗結果: 
      checksum_v1()和checksum_v2() 並沒差,不需要改變。

    • 守則 : 不能把 int加法的值存在short內,即便知道 short不會overflow

short checksum_v3 (int *data) {
        unsigned int i;
        short sum = 0;
        for (i=0; i<64; i++) {
                sum = (short) ( sum +data[i]);
        }   

        return sum;
}

short checksum_v4 (short *data) {
        unsigned int i;
        int sum = 0;
        for (i=0; i <64; i++) {
                sum += *(data ++);
        }
        return (short) sum;
}

理由 :
    compiler會多花幾個arm 指令把值塞入 short內

實驗結果: 
     在checksum_v3() 的迴圈內會多花一個指令 (uxth - zero extend halfword. Extend 16-bit value to 32-bit value)去把short先變成 int。離開迴圈後,再用另一個指令 (sxth - sign extend halfword) 去把 int變為short。

    • 守則 : 使用除法時,盡量使用無符號數

int average_v1 ( int a, int b) {
        return (a+b) /2
}

理由: 
    對負數除二時,需要先加一然後再往右shift一位。ex:  -3/2 = -1 然而 -3 >> 1 = -2,所以需要先加一然後再移位。

實驗結果:
     依然有效

  • C迴圈結構
    • 過時守則 : For Loop內要使用index時,要用遞減方式

int checksum_v5 (int *data) {
        unsigned int i;
        int sum = 0;
        for ( i = 0; i< 64;i++) {
                sum += *(data++);
        }   
        return sum;
}


int checksum_v6 (int *data) {
        unsigned int i;
        int sum = 0;
        for( i=64; i!=0; i--) {
                sum+=*(data++);
        }   
        return sum;
}

理由 : 
    當時的GCC會多花一個指令處理遞增For Loop的index 。

實驗結果 :
    不需使用遞減方式,現在的GCC已經最佳化了,不需要考慮這個。

    • 守則 : 使用 do {} while ()替代 for loop

int checksum_v7 (int *data, unsigned int N) {
        int sum;
        for(; N!=0; N--) {
                sum += *(data++);
        }
        return sum;
}

int checksum_v8 (int *data, unsigned int N) {
        int sum;
        do {
                sum += *(data++);
        } while( --N !=0);
        return sum;
}

理由 : 
    For Loop在第一次迴圈時,會檢查index N是否為 0,但是通常N 不為 0,所以會多花一個arm 指令。 

實驗結果 :
    仍然有效

  • 迴圈展開
    • 守則 : 把迴圈內的內容展開,如此可以減少判斷終止的次數

int checksum_v9 (int *data, unsigned int N ) { 
    int sum = 0;
    do {
        sum += *(data++);
        sum += *(data++);
        sum += *(data++);
        sum += *(data++);
        N -= 4;
    } while (N != 0); 
    return sum;
}


理由 :
     當迴圈的指令數很少時,判斷中止條件的指令就佔很大的比例。將迴圈的內容展開,可以稀釋中止條件所佔的比例。

實驗結果 :
    仍然有效