register傀蚌、volatile基显、restrict與編譯優(yōu)化

register

使用修飾符register聲明的變量屬于寄存器存儲類型。該類型與自動存儲類型相似善炫,具有自動存儲時期撩幽、代碼塊作用域和內(nèi)連接。聲明為register僅僅是一個請求,因此該變量仍然可能是普通的自動變量窜醉。無論哪種情況宪萄,用register修飾的變量都無法獲取地址。如果沒有被初始化榨惰,它的值是未定的拜英。

#include <stdio.h>
int main(int argc, char** argv) {
    register int a = 1;
    printf("%lld", &a);
    return 0;
}

如上代碼編譯使用GCC不通過gcc main.c -o main

image.png

我們換一種方式g++ main.c -o main

image.png

編譯通過且輸出了數(shù)值,看起來像是內(nèi)存地址读串。原來C++中寄存器變量在內(nèi)存中存在副本聊记,這里打印副本的地址。

volatile

volatile告訴編譯器該被變量除了可被程序修改外恢暖,還可能被其他代理排监、線程修改。因此杰捂,當(dāng)使用volatile聲明的變量的值的時候舆床,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),而不使用寄存器中的緩存的值嫁佳。

#include <stdio.h>
int main(int argc, char** argv) {
    volatile int a = 1;
    printf("%llu", &a);
    return 0;
}

結(jié)果輸出地址:

image.png

volatile總是與優(yōu)化有關(guān)挨队,編譯器有一種技術(shù)叫做數(shù)據(jù)流分析,分析程序中的變量在哪里賦值蒿往、在哪里使用盛垦、在哪里失效,分析結(jié)果可以用于常量合并瓤漏,常量傳播等優(yōu)化腾夯,進一步可以死代碼消除。但有時這些優(yōu)化不是程序所需要的蔬充,這時可以用volatile關(guān)鍵字禁止做這些優(yōu)化蝶俱,volatile的字面含義是易變,作用在三個方面:

  1. 不會在兩個操作之間把volatile變量緩存在寄存器中饥漫。在多任務(wù)榨呆、中斷、甚至setjmp環(huán)境下庸队,變量可能被其他的程序改變积蜻,編譯器自己無法知道,volatile就是告訴編譯器這種情況
  2. 不做常量合并彻消、常量傳播等優(yōu)化
  3. volatile變量的讀寫不會被優(yōu)化掉浅侨。如果你對一個變量賦值但后面沒用到,編譯器常持づ颍可以省略那個賦值操作如输,然而對Memory Mapped IO的處理是不能這樣優(yōu)化
應(yīng)該使用volatile的地房
  1. 中斷服務(wù)程序中修改的供其它程序檢測的變量需要加volatile
  2. 多任務(wù)環(huán)境下各任務(wù)間共享的標志應(yīng)該加volatile
  3. 存儲器映射的硬件寄存器通常也要加voliate,因為每次對它的讀寫都可能有不同意義
volatile的配合使用
  1. 可以和const配合使用,例如只讀的狀態(tài)寄存器不见,它是volatile因為它可能被意想不到地改變澳化,它是const因為程序不應(yīng)該試圖去修改它
  2. 指針也可以是volatile,當(dāng)一個服務(wù)中子程序修該一個指向一個buffer的指針時

restrict

restrictC99引入的稳吮,它只可以用于限定指針缎谷,并表明指針是訪問一個數(shù)據(jù)對象的唯一且初始的方式。這個關(guān)鍵字據(jù)說來源于古老的FORTRAN灶似,主要用來修飾指針指向的內(nèi)存不能被別的指針引用列林。所有修改該指針所指向內(nèi)存中內(nèi)容的操作都必須通過該指針來修改,而不能通過其它途徑(其它變量或指針)來修改。這樣做的好處是酪惭,能幫助編譯器進行更好的優(yōu)化代碼,生成更有效率的匯編代碼希痴。如 int *restrict ptr, ptr 指向的內(nèi)存單元只能被ptr訪問到,任何同樣指向這個內(nèi)存單元的其他指針都是未定義的春感,直白點就是無效指針砌创。restrict的出現(xiàn)是因為C語言本身固有的缺陷,C程序員應(yīng)當(dāng)主動地規(guī)避這個缺陷鲫懒,而編譯器也會很配合地優(yōu)化你的代碼嫩实。如下代碼示例:

#include <stdio.h>
int main(int argc, char** argv) {
    int a[5];
  int * restrict ra = (int*) malloc(5 * sizeof(int));
  int * pa = a;
    for(int n = 0; n < 10; n++) {
      pa[n] += 5;
      ra[n] += 5;
      a[n] *= 2;
      pa[n] += 3;
      ra[n] += 3;
  }
}

分析面的代碼,因為ra是訪問分配的內(nèi)存的唯一且初始的方式窥岩,那么編譯器可以將上述對ra的操作進行優(yōu)化:
  ra[n] += 8;
pa并不是訪問數(shù)組a的唯一方式甲献,因此并不能進行下面的優(yōu)化:
  pa[n] +=8;
因為在pa[n] += 3;前,a[n]* = 2;進行了改變颂翼。使用了關(guān)鍵字restrict晃洒,編譯器就可以放心地進行優(yōu)化。

內(nèi)存拷貝函數(shù)

C庫中有兩個函數(shù)可以從一個位置把字節(jié)復(fù)制到另一個位置疚鲤。在C99標準下锥累,它們的原型如下:
  void * memcpy(void * restrict s1, const void * restrict s2, size_t n);
  void * memove(void * s1, const void * s2, size_t n);
這兩個函數(shù)均從s2指向的位置復(fù)制n字節(jié)數(shù)據(jù)到s1指向的位置缘挑,且均返回s1的值集歇。兩者之間的差別由關(guān)鍵字restrict造成,即memcpy()可以假定兩個內(nèi)存區(qū)域沒有重疊语淘,memmove()函數(shù)則不做這個假诲宇。因此,復(fù)制過程類似于首先將所有字節(jié)復(fù)制到一個臨時緩沖區(qū)惶翻,然后再復(fù)制到最終目的地姑蓝。如果兩個區(qū)域存在重疊時使用 memcpy()會怎樣?其行為是不可預(yù)知的吕粗,既可以正常工作纺荧,也可能失敗。在不應(yīng)該使用memcpy()時,編譯器不會禁止使用memcpy()宙暇。因此输枯,使用memcpy()時,您必須確保沒有重疊區(qū)域占贫。這是程序員的任務(wù)的一部分桃熄,驗證結(jié)果如下代碼:

編譯優(yōu)化

硬件優(yōu)化

內(nèi)存訪問速度遠不及CPU處理速度,為提高機器整體性能型奥,在硬件上引入硬件高速緩存Cache瞳收,加速對內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴格按照順序執(zhí)行厢汹,沒有相關(guān)性的指令可以亂序執(zhí)行螟深,以充分利用CPU的指令流水線,提高執(zhí)行速度坑匠。以上是硬件級別的優(yōu)化血崭。

軟件優(yōu)化

一種是在編寫代碼時由程序員優(yōu)化,另一種是由編譯器進行優(yōu)化厘灼。編譯器優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器夹纫;調(diào)整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令设凹。對常規(guī)內(nèi)存進行優(yōu)化的時候舰讹,這些優(yōu)化是透明的,而且效率很好闪朱。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory barrier)月匣,linux提供了一個宏解決編譯器的執(zhí)行順序問題。
void Barrier(void)
這個函數(shù)通知編譯器插入一個內(nèi)存屏障奋姿,但對硬件無效锄开,編譯后的代碼會把當(dāng)前CPU寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時候再重新從內(nèi)存中讀出称诗。

linux中的編譯優(yōu)化

上面我們已經(jīng)看了一點編譯優(yōu)化的??萍悴。我們先看如下的代碼:

#include <stdio.h>

int thread_func(LPVOID signal) {
    int* intSignal = reinterpret_cast<int*>(signal); 
    *intSignal = 2; 
    while(*intSignal != 1) 
        return 0; 
}

int main(int argc, char** argv) {
    thread_func(singal)
    return 0;
}

該線程啟動時將intSignal置為2,然后循環(huán)等待直到intSignal為1 時退出寓免。顯然intSignal的值必須在外部被改變癣诱,否則該線程不會退出。但是實際運行的時候該線程卻不會退出袜香,即使在外部將它的值改為1撕予。這是一個線程優(yōu)化的例子,下面看一個簡單的賦值優(yōu)化:

#include <stdio.h>
int main (void) {
    int i = 5;
    int a = i; //優(yōu)化
    return 0;
}
int main(int argc, char** argv) {
    volatile int i = 5;
    int a = i; //優(yōu)化
    return 0;
}

兩段代碼寫在一起蜈首,自行分開∈德眨現(xiàn)在分別編譯他們:

  • 有編譯的優(yōu)化欠母,沒有volatile關(guān)鍵字
    .section __TEXT,__text_startup,regular,pure_instructions
    .align 4
    .globl _main
_main:
LFB1:
    xorl    %eax, %eax
    ret
LFE1:
    .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
    .set L$set$0,LECIE1-LSCIE1
    .long L$set$0
LSCIE1:
    .long   0
    .byte   0x1
    .ascii "zR\0"
    .byte   0x1
    .byte   0x78
    .byte   0x10
    .byte   0x1
    .byte   0x10
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .byte   0x90
    .byte   0x1
    .align 3
LECIE1:
LSFDE1:
    .set L$set$1,LEFDE1-LASFDE1
    .long L$set$1
LASFDE1:
    .long   LASFDE1-EH_frame1
    .quad   LFB1-.
    .set L$set$2,LFE1-LFB1
    .quad L$set$2
    .byte   0
    .align 3
LEFDE1:
    .subsections_via_symbols
  • 無編譯優(yōu)化,有volatile關(guān)鍵字
    .section __TEXT,__text_startup,regular,pure_instructions
    .align 4
    .globl _main
_main:
LFB1:
    movl    $5, -4(%rsp)
    movl    -4(%rsp), %eax
    xorl    %eax, %eax
    ret
LFE1:
    .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
    .set L$set$0,LECIE1-LSCIE1
    .long L$set$0
LSCIE1:
    .long   0
    .byte   0x1
    .ascii "zR\0"
    .byte   0x1
    .byte   0x78
    .byte   0x10
    .byte   0x1
    .byte   0x10
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .byte   0x90
    .byte   0x1
    .align 3
LECIE1:
LSFDE1:
    .set L$set$1,LEFDE1-LASFDE1
    .long L$set$1
LASFDE1:
    .long   LASFDE1-EH_frame1
    .quad   LFB1-.
    .set L$set$2,LFE1-LFB1
    .quad L$set$2
    .byte   0
    .align 3
LEFDE1:
    .subsections_via_symbols

觀察發(fā)現(xiàn)在LFB1段是不同的吆寨,使用volatile的代碼編譯未優(yōu)化艺蝴,還在訪問內(nèi)存,而優(yōu)化過的代碼鸟废,沒有訪問內(nèi)存猜敢,只有寄存器。

優(yōu)化的編譯參數(shù)-O

-O設(shè)置一共有五種:-O0盒延、-O1缩擂、-O2、-O3和-Os添寺,只能設(shè)置一種胯盯。除了-O0以外,每一個-O設(shè)置都會多啟用幾個選項计露,請查閱gcc手冊的優(yōu)化選項章節(jié)博脑,以便了解每個-O等級啟用了哪些選項及它們有何作

  • -O0: 這個等級(字母“O”后面跟個零)關(guān)閉所有優(yōu)化選項,也是CFLAGSCXXFLAGS中沒有設(shè)置-O等級時的默認等級票罐。這樣就不會優(yōu)化代碼叉趣,這通常不是我們想要的
  • -O1: 這是最基本的優(yōu)化等級。編譯器會在不花費太多編譯時間的同時試圖生成更快更小的代碼该押。這些優(yōu)化是非沉粕迹基礎(chǔ)的,但一般這些任務(wù)肯定能順利完成
  • -O2: -O1的進階蚕礼。這是推薦的優(yōu)化等級烟具,除非你有特殊的需求。-O2會比-O1啟用多一些標記奠蹬。設(shè)置了-O2后朝聋,編譯器會試圖提高代碼性能而不會增大體積和大量占用的編譯時間
  • -O3: 這是最高最危險的優(yōu)化等級。用這個選項會延長編譯代碼的時間囤躁,并且在使用gcc4.x的系統(tǒng)里不應(yīng)全局啟用冀痕。自從3.x版本以來gcc的行為已經(jīng)有了極大地改變。在3.x割以,-O3生成的代碼也只是比-O2快一點點而已金度,而gcc4.x中還未必更快应媚。用-O3來編譯所有的軟件包將產(chǎn)生更大體積更耗內(nèi)存的二進制文件严沥,大大增加編譯失敗的機會或不可預(yù)知的程序行為(包括錯誤)。這樣做將得不償失中姜,記住過猶不及消玄。在gcc 4.x.中使用-O3是不推薦的
  • -Os: 這個等級用來優(yōu)化代碼尺寸跟伏。其中啟用了-O2中不會增加磁盤空間占用的代碼生成選項。這對于磁盤空間極其緊張或者CPU緩存較小的機器非常有用翩瓜。但也可能產(chǎn)生些許問題受扳,因此軟件樹中的大部分ebuild都過濾掉這個等級的優(yōu)化。使用-Os是不推薦的

下面是一個示例兔跌,編譯gcc -O0 -S main.c

#include <stdio.h>
int main (void) {
    int i = 5;
    int a = i; //優(yōu)化
    return 0;
}

編譯出來結(jié)果:

    .text
    .globl _main
_main:
LFB1:
    pushq   %rbp
LCFI0:
    movq    %rsp, %rbp
LCFI1:
    movl    %edi, -20(%rbp)
    movq    %rsi, -32(%rbp)
    movl    $5, -4(%rbp)
    movl    -4(%rbp), %eax
    movl    %eax, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
LCFI2:
    ret
LFE1:
    .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
    .set L$set$0,LECIE1-LSCIE1
    .long L$set$0
LSCIE1:
    .long   0
    .byte   0x1
    .ascii "zR\0"
    .byte   0x1
    .byte   0x78
    .byte   0x10
    .byte   0x1
    .byte   0x10
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .byte   0x90
    .byte   0x1
    .align 3
LECIE1:
LSFDE1:
    .set L$set$1,LEFDE1-LASFDE1
    .long L$set$1
LASFDE1:
    .long   LASFDE1-EH_frame1
    .quad   LFB1-.
    .set L$set$2,LFE1-LFB1
    .quad L$set$2
    .byte   0
    .byte   0x4
    .set L$set$3,LCFI0-LFB1
    .long L$set$3
    .byte   0xe
    .byte   0x10
    .byte   0x86
    .byte   0x2
    .byte   0x4
    .set L$set$4,LCFI1-LCFI0
    .long L$set$4
    .byte   0xd
    .byte   0x6
    .byte   0x4
    .set L$set$5,LCFI2-LCFI1
    .long L$set$5
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .align 3
LEFDE1:
    .subsections_via_symbols

觀察LCFI1段勘高,發(fā)現(xiàn)仍然在訪問內(nèi)存,不像加了編譯優(yōu)化級別-O2時只是訪問寄存器坟桅。這只是編譯優(yōu)化的一個小例子华望,實際中還有許多其他的可以優(yōu)化的內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仅乓,一起剝皮案震驚了整個濱河市赖舟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夸楣,老刑警劉巖宾抓,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豫喧,居然都是意外死亡石洗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門紧显,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劲腿,“玉大人,你說我怎么就攤上這事鸟妙〗谷耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵重父,是天一觀的道長花椭。 經(jīng)常有香客問我,道長房午,這世上最難降的妖魔是什么矿辽? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮郭厌,結(jié)果婚禮上袋倔,老公的妹妹穿的比我還像新娘。我一直安慰自己折柠,他們只是感情好宾娜,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扇售,像睡著了一般前塔。 火紅的嫁衣襯著肌膚如雪嚣艇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天华弓,我揣著相機與錄音食零,去河邊找鬼。 笑死寂屏,一個胖子當(dāng)著我的面吹牛贰谣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迁霎,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼冈爹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了欧引?” 一聲冷哼從身側(cè)響起频伤,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芝此,沒想到半個月后憋肖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡婚苹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年岸更,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膊升。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡怎炊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出廓译,到底是詐尸還是另有隱情评肆,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布非区,位于F島的核電站瓜挽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏征绸。R本人自食惡果不足惜久橙,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管怠。 院中可真熱鬧淆衷,春花似錦、人聲如沸渤弛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暮芭。三九已至鹿驼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辕宏,已是汗流浹背畜晰。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瑞筐,地道東北人凄鼻。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像聚假,于是被迫代替她去往敵國和親块蚌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,105評論 1 32
  • 1.設(shè)計模式是什么? 你知道哪些設(shè)計模式瘪贱,并簡要敘述纱控?設(shè)計模式是一種編碼經(jīng)驗,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,158評論 0 12
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用菜秦,...
    LZM輪回閱讀 3,315評論 0 6
  • 最全的iOS面試題及答案 iOS面試小貼士 ———————————————回答好下面的足夠了-----------...
    zweic閱讀 2,703評論 0 73
  • 多線程甜害、特別是NSOperation 和 GCD 的內(nèi)部原理。運行時機制的原理和運用場景球昨。SDWebImage的原...
    LZM輪回閱讀 2,008評論 0 12