2011-03-22 19 views
3

我正在爲ARM9處理器編寫一些日誌記錄C代碼。如果存在動態模塊,該代碼將記錄一些數據。該模塊通常不會出現在生產版本中,但日誌代碼將始終編譯進來。想法是,如果客戶遇到錯誤,我們可以加載此模塊,並且日誌記錄代碼將轉儲調試信息。使用RVCT4.0對Arm9進行靜態分支預測

當模塊不存在時,日誌代碼必須產生最小的影響,因此每個週期都會計數。在一般情況下,日誌代碼看起來是這樣的:

__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (NULL == pProvider) 
     return; 
    ... logging code goes here ... 
} 

隨着優化,RVCT 4.0生成的代碼如下所示:

ldr  r4,[r0,#0x2C]  ; pProvider,[r0,#44] 
cmp  r4,#0x0   ; pProvider,#0 
beq  0x23BB4BE (usually taken) 
... logging code goes here... 
... regular code starts at 0x23BB4BE 

該處理器沒有分支預測,我的理解是,每次採取分支時都會有2個週期的懲罰(如果不採取分支則不會受到懲罰)。

我想普通的情況下,其中NULL == pProvider,快速的情況下,分支不採取。我如何使RVCT 4.0生成這樣的代碼?

我使用__builtin_expect如下嘗試:

if (__builtin_expect(NULL == pProvider, 1)) 
    return; 

不幸的是,這對生成的代碼沒有任何影響。我錯誤地使用了__builtin_expect嗎?有沒有另一種方法(希望沒有內聯彙編)?

+0

此代碼沒有意義。如果pProvider是第一個arg,並且在不被解引用的情況下針對NULL進行檢查,則編譯器不需要ldr,因爲pProvider已經在r0中。看起來您正在查看Provider類型的偏移量。 – 2011-03-23 06:21:16

+0

@變長編碼器,日誌功能是內聯的,所以不用擔心參數。 ldr指令從某些數據結構中提取pProvider。如果函數未被內聯,則會在調用之前發生。 – 2011-03-23 16:31:20

+0

作爲參考,RVCT 3.0文檔表明支持[__builtin_expect](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0202h/Cjabddedbde.html) – 2011-03-23 16:34:56

回答

1

因此,如果沒有分支預測器,並且在分支時受到兩個週期的懲罰,爲什麼不相應地重寫程序來做到這一點? (嗯,事實上你會認爲你上面的例子應該已經導致了「正確」的代碼,但我們可以試試)

__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (pProvider) { 
     ... logging code goes here ... 
    } 
} 

說,「可能」編譯爲:

ldr  r4,[r0,#0x2C]  ; pProvider,[r0,#44] 
cmp  r4,#0x0   ; pProvider,#0 
bneq  logging_code (usually NOT taken) 
... regular code here 
logging_code: .. well anywhere 

,如果你」幸運的是,即使現在這樣做了,對編譯器的每一次改變都可能改變它,而且我不知道它是否會導致彙編代碼使用你正在使用的任何編譯器。 因此,無論如何可能寫在內聯程序集?沒有那麼多的代碼和gcc(以及VC,我假設其他人也這麼做)使得這很容易。最簡單的,你只希望你的日誌代碼定義一個額外的方法調用(關於ARM ABI不知道,所以你必須編寫你自己)

+0

我嘗試重寫日誌記錄代碼,但RVCT會以任一方式生成相同的代碼。使用內聯彙編重寫可能是要走的路,但我不確定編譯器是否會尊重它,因爲這是塊佈局問題。無論如何謝謝你的建議。 – 2011-03-22 23:27:57

+0

是的編譯器優化可能會有問題。不適用於RVCT,但gcc允許您使用__ volatile __(不含空格;但否則它只是使其爲粗體)關鍵字,以確保編譯器僅保留代碼(基本上意味着它不會移動/刪除代碼)。 – Voo 2011-03-23 00:23:49

1

如果您使用下面的結構:

void log_some_stuff_implementation(Provider *pProvider, int x, int y, char const* str); 


__inline void log_some_stuff(Provider *pProvider, int x, int y, char const* str) 
{ 
    if (__builtin_expect(pProvider != NULL, 0)) { 
     log_some_stuff_implementation(pProvider, x, y, str); 
    } 

    return; 
} 

GCC 4.5.2與-O2生成以下代碼(至少對於我的簡單測試),用於調用log_some_stuff()

// r0 already has the Provider* in it - r2 has a value that indicates whether 
//  r0 was loaded with a valid pointer or not 
cmp r2, #0 
ldrne r3, [r1, #0] 
addne r1, r2, #1 
ldrneb r2, [r3, #0] @ zero_extendqisi2 
blne log_some_stuff_implementation 

因此在通常情況下(其中提供者*是NULL),4所說明由於有條件,使用但未執行ARM流水線不會被刷新。我認爲這可能與您在實際上不希望日誌代碼運行的常見情況下所獲得的結果一樣好。

我認爲關鍵是實際上做記錄工作的代碼在一個單獨的函數中以非內聯方式完成,因此編譯器可以合理地將該函數的設置和調用作爲一些有條件地執行的指令的內聯序列。由於實際的日誌記錄代碼不需要優化,因此沒有理由將其內聯。這不應該是常見的情況,大概是代碼會做一些真正的工作。因此,函數調用的開銷應該是可以接受的(至少這是我的假設)。順便說一句,對於我的簡單測試,即使__builtin_expect()被遺漏,也會生成相同的代碼序列(或基本上相同的序列),但是我想像在比我的簡單測試更復雜的序列中,內建可能會幫助編譯出來。所以我可能會留在,但我也可能會使用更可讀的版本,如Linux內核的宏:

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 
+0

不幸的是,我們正在使用RVCT,而不是GCC。 RVCT文檔指出__builtin_expect應該可以工作,但它不起作用。儘管如此,我會接受關於可能/不太可能的宏的建議。 – 2011-03-23 16:28:25

+0

即使使用GCC,也不管__builtin_expect()如何生成相同的代碼,但我的測試非常簡單。我關於離開它的評論是爲了在更復雜的情況下可以幫助編譯器。我認爲關鍵是保持內嵌的'log_some_stuff()'函數足夠短,以至於編譯器使用條件操作碼有效地使日誌記錄在常見情況下只有少數幾個NOP是有意義的。如果內聯函數太長,那麼NOP會在分支後重新填充管道的時候花費很多或更多的週期,所以避免分支將不會有增益。 – 2011-03-23 19:53:32

0

你的分支優化是要獲得你很少。你可以獲得更多,如果你做到以下幾點:

#define log_some_stuff(pProvider, other_arg) \ 
     do {\ 
      if(pProvider != NULL) \ 
       real_log_some_stuff(pProvider, other_arg); \ 
     } \ 
     while(0) 

這將做的是它將內聯空調檢查到所有的調用代碼。這可能看起來像是一種損失,但真正發生的是編譯器可以避免函數調用的開銷,包括推送寄存器,分支本身以及通過簡單的NULL檢查使r0-r3和lr失效(您會無論如何不得不這樣做)。總的來說,我敢打賭,這將比通過提前退出一條指令而節省的單一週期獲得更多的收益。

+1

使用宏而不是僅僅內聯函數(他已經在做什麼)應該是相同的結果,並且解決了所有用宏運行的可怕問題。沒有看到優勢 – Voo 2011-03-23 15:11:56

+0

我已驗證該功能已被內聯。我在上面發佈的指令實際上是正常情況下爲日誌記錄功能執行的唯一三條指令。 – 2011-03-23 16:22:34

0

您可以使用goto

__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (pProvider != NULL) 
     goto LOGGING; 
    return; 
LOGGING: 
    ... logging code goes here ... 
} 

使用__builtin_expect是更容易,但我不知道RVCT有它。