LLVM編譯器中的內(nèi)置(built-in)函數(shù)

什么是built-in 函數(shù)?

在一些.h頭文件中或者實現(xiàn)代碼中經(jīng)常會看到一些以__builtin_開頭的函數(shù)聲明或者調(diào)用,比如下面的頭文件#include <secure/_string.h>中的函數(shù)定義:

//這里的memcpy函數(shù)的由內(nèi)置函數(shù)__builtin___memcpy_chk來實現(xiàn)。
#if __has_builtin(__builtin___memcpy_chk) || defined(__GNUC__)
#undef memcpy
/* void *memcpy(void *dst, const void *src, size_t n) */
#define memcpy(dest, ...) \
        __builtin___memcpy_chk (dest, __VA_ARGS__, __darwin_obsz0 (dest))
#endif

這些__builtin_開頭的符號其實是一些編譯器內(nèi)置的函數(shù)或者編譯優(yōu)化處理開關等,其作用類似于宏。宏是高級語言用于預編譯時進行替換的源代碼塊,而內(nèi)置函數(shù)則是用于在編譯階段進行替換的機器指令塊。因此編譯器的這些內(nèi)置函數(shù)其實并不是真實的函數(shù),而只是一段指令塊,起到編譯時的內(nèi)聯(lián)功能。

內(nèi)置函數(shù)和非內(nèi)置函數(shù)的調(diào)用的區(qū)別

在一些編譯器中會對一些標準庫的函數(shù)實現(xiàn)改用內(nèi)置函數(shù)來代替,可以起到性能優(yōu)化的作用。因為執(zhí)行這些函數(shù)調(diào)用會在編譯時變?yōu)橹苯又噶顗K的執(zhí)行,而不會產(chǎn)生指令跳轉(zhuǎn)、堆棧等相關的操作而引起的函數(shù)調(diào)用開銷(有一些函數(shù)直接就有一條對應的機器指令來實現(xiàn),如果改用普通函數(shù)調(diào)用勢必性能大打折扣)。不同的編譯器對內(nèi)置函數(shù)的支持不盡相同,而且對于是否用內(nèi)置函數(shù)來實現(xiàn)標準庫函數(shù)也沒有統(tǒng)一的標準。比如對于GCC來說它所支持的內(nèi)置函數(shù)都在GCC內(nèi)置函數(shù)列表中被定義和聲明,這些內(nèi)置函數(shù)大部分也被LLVM編譯器所支持。

本文不會介紹所有的內(nèi)置函數(shù),而是只介紹其中幾個特殊的內(nèi)置函數(shù)以及使用方法。熟練使用這些內(nèi)置函數(shù)可以提升程序的運行性能以及擴展一些編程的模式。

  • __builtin_types_compatible_p()
    這個函數(shù)用來判斷兩個變量的類型是否一致,如果一致返回true否則返回false。這里的變量會忽略一些修飾關鍵字,比如const int 和 int 會被認為是相同的變量類型??梢杂眠@個函數(shù)來判斷某個變量是否是特定的類型,還可以用這個函數(shù)來做一些類型檢查相關的防護邏輯。一般這個函數(shù)都和typeof關鍵字一起使用。
 int a, b
 long c;

int ret1= __builtin_types_compatible_p(typeof(a), typeof(b));  //true
int ret2 = __builtin_types_compatible_p(typeof(a), typeof(c)); //false 
int ret3 = __builtin_types_compatible_p(int , const int);  //true
if  (__builtin_types_compatible_p(typeof(a), int))   //true
{
    
}

  • __builtin_constant_p()
    這個函數(shù)用來判斷某個表達式是否是一個常量,如果是常量返回true否則返回false。
   int a = 10;
   const int b = 10;
   int ret1  = __builtin_constant_p(10);  //true
   int ret2 = __builtin_constant_p(a);  //false
   int ret3 = __builtin_constant_p(b);  //true
  • __builtin_offsetof()
    這個函數(shù)用來獲取一個結(jié)構(gòu)體成員在結(jié)構(gòu)中的偏移量。函數(shù)的第一個參數(shù)是結(jié)構(gòu)體類型,第二個參數(shù)是其中的數(shù)據(jù)成員的名字。
struct S
{
    char m_a;
    long m_b;
};

int offset1 = __builtin_offsetof(struct S, m_a);  //0
int offset2 = __builtin_offsetof(struct S, m_b);  //8

struct S s;
s.m_a = 'a';
s.m_b = 10;
    
char m_a = *(char*)((char*)&s + offset1);   //'a'
long m_b = *(long*)((char*)&s + offset2);  // 10
  • __builtin_return_address()
    這個函數(shù)返回調(diào)用函數(shù)的返回地址,參數(shù)為調(diào)用返回的層級,從0開始,并且只能是一個常數(shù)。假如有一個函數(shù)調(diào)用棧為A->B->C->D。那么在D函數(shù)內(nèi)調(diào)用__builtin_return_address(0)返回的是C函數(shù)調(diào)用D函數(shù)的下一條指令的地址,如果調(diào)用的是__builtin_return_address(1)則返回B函數(shù)調(diào)用C函數(shù)的下一條指令的地址,依次類推。這個函數(shù)的一個應用場景是被調(diào)用者內(nèi)部可以根據(jù)外部調(diào)用者的不同而進行差異化處理。
//這個例子演示一個函數(shù)foo。如果是被fout1函數(shù)調(diào)用則返回1,被其他函數(shù)調(diào)用時則返回0。

#include <dlfcn.h>

extern int foo();

void fout1()
{
    printf("ret1 = %d\n", foo());    //ret1 = 1
}

void fout2()
{
    printf("ret2 = %d\n", foo());    //ret2= 0
}

int foo()
{
     void *retaddr = __builtin_return_address(0);  //這個返回地址就是調(diào)用者函數(shù)的某一處的地址。  
    //根據(jù)返回地址可以通過dladdr函數(shù)獲取調(diào)用者函數(shù)的信息。
    Dl_info dlinfo;
    dladdr(retaddr, &dlinfo);
    if (dlinfo.dli_saddr == fout1)
        return 1;
    else
        return 0;
}

__builtin_return_address()函數(shù)的另外一個經(jīng)典的應用是iOS系統(tǒng)中用ARC進行內(nèi)存管理時對返回值是OC對象的函數(shù)和方法的特殊處理。比如一個函數(shù)foo返回一個OC對象時,系統(tǒng)在編譯時會對返回的對象調(diào)用objc_autoreleaseReturnValue函數(shù),而在調(diào)用foo函數(shù)時則會在編譯時插入如下的三條匯編指令:

//arm64位的指令
bl foo
mov fp, fp    //這條指令看似無意義,其實這是一條特殊標志指令。
bl objc_retainAutoreleasedReturnValue

如果考察objc_autoreleaseReturnValue函數(shù)的內(nèi)部實現(xiàn)就會發(fā)現(xiàn)其內(nèi)部用了__builtin_return_address函數(shù)。objc_autoreleaseReturnValue函數(shù)通過調(diào)用__builtin_return_address(0)返回的地址的內(nèi)容是否是mov fp,fp來進行特殊的處理。具體原理可以參考這些函數(shù)的實現(xiàn),因為它們都已經(jīng)開源。

  • __builtin_frame_address()
    這個函數(shù)返回調(diào)用函數(shù)執(zhí)行時棧內(nèi)存為其分配的棧幀(stack frame)區(qū)間中的高位地址值。參數(shù)為調(diào)用函數(shù)的層級,從0開始并且只能是一個常數(shù)。這個函數(shù)可以用來實現(xiàn)防止棧內(nèi)存溢出的棧保護處理。因為調(diào)用函數(shù)內(nèi)定義的任何的局部變量的地址都必須要小于這個地址值。
void foo(char *buf)
{
   void *frameaddr =  __builtin_frame_address(0);
  
   //定義棧內(nèi)存變量,長度為100個字節(jié)。
   char local[100];

   int buflen = strlen(buf);   //獲取傳遞進來的緩存字符串的長度。
   if (local + buflen > frameaddr)  //進行棧內(nèi)存溢出判斷。
   {
         ptrinf("可能會出現(xiàn)棧內(nèi)存溢出");
         return;
   }
   
  strcpy(local, buf);
}

  • __builtin_choose_expr()
    這個函數(shù)主要用于實現(xiàn)在編譯時進行分支判斷和選擇處理,從而可以實現(xiàn)在編譯級別上的函數(shù)重載的能力。函數(shù)的格式為:
    __builtin_choose_expr(exp, e1, e2)
    其所表達的意思是判斷表達式exp的值,如果值為真則使用e1代碼塊的內(nèi)容,而如果值為假時則使用e2代碼塊的內(nèi)容。這個函數(shù)一般都和__builtin_types_compatible_p函數(shù)一起使用,將類型判斷作為表達式參數(shù)。比如下面的代碼:
void fooForInt(int a)
{
    printf("int a = %d\n", a);
}

void fooForDouble(double a)
{
    printf("double a=%f\n", a);
}

//如果x的數(shù)據(jù)類型是整型則使用fooForInt函數(shù),否則使用fooForDouble函數(shù)。
#define fooFor(x) __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), fooForInt(x), fooForDouble(x))

//根據(jù)傳遞進入的參數(shù)類型來決定使用哪個具體的函數(shù)。
fooFor(10);
fooFor(10.0);

  • __builtin_expect()
    這個函數(shù)的主要作用是進行條件分支預測。 函數(shù)主要有兩個參數(shù): 第一個參數(shù)是一個布爾表達式、第二個參數(shù)表明第一個參數(shù)的值為真值的概率,這個參數(shù)只能取1或者0,當取值為1時表示布爾表達式大部分情況下的值是真值,而取值為0時則表示布爾表達式大部分情況下的值是假值。函數(shù)的返回就是第一個參數(shù)的表達式的值。
    在一條指令執(zhí)行時,由于流水線的作用,CPU可以完成下一條指令的取指,這樣可以提高CPU的利用率。在執(zhí)行一條條件分支指令時,CPU也會預取下一條執(zhí)行,但是如果條件分支跳轉(zhuǎn)到了其他指令,那CPU預取的下一條指令就沒用了,這樣就降低了流水線的效率。__builtin_expect 函數(shù)可以優(yōu)化程序編譯后的指令序列,使指令盡可能的順序執(zhí)行,從而提高CPU預取指令的正確率。例如:
if (__builtin_expect (x, 0))
     foo ();

表示x的值大部分情況下可能為假,因此foo()函數(shù)得到執(zhí)行的機會比較少。這樣編譯器在編譯這段代碼時就不會將foo()函數(shù)的匯編指令緊挨著if條件跳轉(zhuǎn)指令。再例如:

if (__builtin_expect (x, 1))
     foo ();

表示x的值大部分情況下可能為真,因此foo()函數(shù)得到執(zhí)行的機會比較大。這樣編譯器在編譯這段代碼時就會將foo()函數(shù)的匯編指令緊挨著if條件跳轉(zhuǎn)指令。

為了簡化函數(shù)的使用,iOS系統(tǒng)的兩個宏fastpath和slowpath來實現(xiàn)這種分支優(yōu)化判斷處理。


#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

本節(jié)參考自:https://blog.csdn.net/jasonchen_gbd/article/details/44948523

  • __builtin_prefetch()
    這個函數(shù)主要用來實現(xiàn)內(nèi)存數(shù)據(jù)的預抓取處理。一般CPU內(nèi)部都會提供幾級高速緩存,在高速緩存中進行數(shù)據(jù)存取要比在內(nèi)存中速度快。因此為了提升性能,可以預先將某個內(nèi)存地址中的數(shù)據(jù)讀取或?qū)懭氲礁咚倬彺嬷腥?,這樣當真實需要對內(nèi)存地址進行存取時實際上是在高速緩存中進行。而__builtin_prefetch函數(shù)就是用來將某個內(nèi)存中的數(shù)據(jù)預先加載或?qū)懭氲礁咚倬彺嬷腥ァ:瘮?shù)的格式如下:
    __builtin_prefetch(addr, rw, locality)
    其中addr就是要進行預抓取的內(nèi)存地址。 rw是一個可選參數(shù)取值只能取0或者1,0表示未來要預計對內(nèi)存進行讀操作,而1表示預計對內(nèi)存進行寫操作。locality 取值必須是常數(shù),也稱為“時間局部性”(temporal locality) 。時間局部性是指,如果程序中某一條指令一旦執(zhí)行,則不久之后該指令可能再被執(zhí)行;如果某數(shù)據(jù)被訪問,則不久之后該數(shù)據(jù)會被再次訪問。該值的范圍在 0 - 3 之間。為 0 時表示,它沒有時間局部性,也就是說,要訪問的數(shù)據(jù)或地址被訪問之后的短時間內(nèi)不會再被訪問;為 3 時表示,被訪問的數(shù)據(jù)或地址具有高 時間局部性,也就是說,在被訪問不久之后非常有可能再次訪問;對于值 1 和 2,則分別表示具有低 時間局部性 和中等 時間局部性。該值默認為 3 。
    一般執(zhí)行數(shù)據(jù)預抓取的操作都是在地址將要被訪問之前的某個時間進行。通過數(shù)據(jù)預抓取可以有效的提高數(shù)據(jù)的存取訪問速度。比如下面的代碼實現(xiàn)對數(shù)組中的所有元素執(zhí)行頻繁的寫之前進行預抓取處理:
//定義一個數(shù)組,在接下來的時間中需要對數(shù)組進行頻繁的寫處理,因此可以將數(shù)組的內(nèi)存地址預抓取到高速緩存中去。
int arr[10];
for (int i = 0; i < 10; i++)
{
     __builtin_prefetch(arr+i, 1, 3);
}

//后面會頻繁的對數(shù)組元素進行寫入處理,因此如果不調(diào)用預抓取函數(shù)的話,每次寫操作都是直接對內(nèi)存地址進行寫處理。
//而當使用了高速緩存后,這些寫操作可能只是在高速緩存中執(zhí)行。
for (int i = 0; i < 1000000; i++)
{
     arr[i%10] = i;
}

本節(jié)參考自:https://blog.csdn.net/chrysanthemumcao/article/details/8907566


歡迎大家訪問歐陽大哥2013的github地址簡書地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 創(chuàng)業(yè)不等于從0到1,而是從0到N的過程 創(chuàng)新中的從0到1只是完成了萬里長城的第一步。真正的創(chuàng)新,是要走完從0到N的...
    丿卿閱讀 691評論 0 0
  • 晴 大概是晴天?上課的一天,被刑分支配的一天。很難受。 沒什么好說的,依舊是越來越聽不下去,連教室里面的人都越來越...
    Cheryl_ak717閱讀 252評論 0 0
  • 第193章回顧 此時雖然已經(jīng)是下午,皇宮里還是一日既往的熱鬧,宮女們在各個殿之間穿梭著,皇宮的軍營里也同樣是一片熙...
    陳瀛Neptune閱讀 387評論 27 22
  • 1 我很喜歡一個人旅游,說走就走,說停就停,感覺比人多時更放松、放空。 至于安全,好女子渾身是膽。跑得過色狼,...
    青梨夜閱讀 2,135評論 2 21
  • 又一年的七夕節(jié)。于我,更多的不是愛情,而是我滿腦子的親情。 七仙女,金牛姑。 關于七夕,我的記憶版本是:七夕這天,...
    peach桃子閱讀 299評論 0 0

友情鏈接更多精彩內(nèi)容