2016-09-21 18 views
11

我有許多小功能,我想內聯,例如測試標誌對於一些條件:Julia - @inline如何工作?何時使用功能與宏?

const COND = UInt(1<<BITS_FOR_COND) 
function is_cond(flags::UInt) 
    return flags & COND != 0 
end 

我也可以做一個宏:

macro IS_COND(flags::UInt) 
    return :(flags & COND != 0) 
end 

我的動機是許多類似的宏功能在C代碼我正在與工作:

#define IS_COND(flags) ((flags) & COND) 

我反覆本身計時的功能,具有@inline定義的宏,功能和表達,但在許多跑步中,沒有人比其他人跑得快。在1)和3)中函數調用的生成代碼比4)中的表達式要長得多,但是我不知道如何比較2),因爲@code_llvm等不適用於其他宏。

1) for j=1:10 @time for i::UInt=1:10000 is_cond(i); end end 
2) for j=1:10 @time for i::UInt=1:10000 @IS_COND(i); end end 
3) for j=1:10 @time for i::UInt=1:10000 is_cond_inlined(i); end end 
4) for j=1:10 @time for i::UInt=1:10000 i & COND != 0; end end 

問題:@inline的用途是什麼?我從稀疏的文檔中看到,它將符號:inline附加到表達式:meta,但究竟是這樣做的呢?有沒有任何理由更喜歡這種類型的任務的功能或宏?

我的理解是,C宏函數只是在編譯時替換宏的文本文本,所以生成的代碼沒有跳轉,因此比常規函數調用更有效。 (安全性是另一個問題,但讓我們假設程序員知道他們在做什麼。)Julia宏具有解析其參數的中間步驟,所以對我來說2)是否應該比1)更快並不明顯。目前忽略在這種情況下,性能差異可以忽略不計,那麼採用哪種技術可獲得最高效的代碼呢?

回答

17

如果這兩種語法導致完全相同的生成代碼,你應該比另一種更適合嗎? 。在這種情況下,函數遠遠優於宏。

  • 宏很強大,但它們很棘手。您的@IS_COND定義中存在三個錯誤(您不希望在參數中放置類型註釋,您需要在返回的表達式中插入flags,並且您需要使用esc以使衛生正確)。
  • 函數的定義就像你期望的那樣。
  • 也許更重要的是,該功能的工作方式與其他預期的一樣。宏可以做任何事情,所以@ sigil是一個很好的警告「超出正常的Julia語法在這裏發生的事情。」如果它的行爲就像一個函數一樣,儘管如此,也可以做到這一點。
  • 函數是Julia中的第一類對象;您可以將它們傳遞並使用它們,例如map等更高階的函數。
  • Julia建立在內聯函數之上。它的性能取決於它!小功能通常不需要註釋 - 它只是自己做。你可以使用@inline給編譯器一個額外的推動,一個更大的函數對內聯特別重要......但是通常Julia擅長自己計算出來(就像這裏)。
  • 回溯和調試在內聯函數中比宏更好。

所以,現在,他們是否得到相同的生成的代碼?關於朱莉婭最有力的事情之一就是你可以要求它做「中級工作」。

首先,一些設置:

julia> const COND = UInt(1<<7) 
     is_cond(flags) = return flags & COND != 0 
     macro IS_COND(flags) 
      return :($(esc(flags)) & COND != 0) # careful! 
     end 

現在我們可以開始尋找當您使用is_cond@IS_COND會發生什麼。在實際的代碼,你將使用在其他函數中這些定義,讓我們創建一些測試功能:

julia> test_func(x) = is_cond(x) 
     test_macro(x) = @IS_COND(x) 

現在我們可以開始向下移動鏈,看看是否有區別。第一步是「降低」 - 這只是將語法轉換爲有限的子集,以使編譯器更輕鬆。你可以看到,在這個階段中,宏被擴展,但該函數調用仍然存在:

julia> @code_lowered test_func(UInt(1)) 
LambdaInfo template for test_func(x) at REPL[2]:1 
:(begin 
     nothing 
     return (Main.is_cond)(x) 
    end) 

julia> @code_lowered test_macro(UInt(1)) 
LambdaInfo template for test_macro(x) at REPL[2]:2 
:(begin 
     nothing 
     return x & Main.COND != 0 
    end) 

下一步,雖然是推理和優化。在這裏函數內聯生效:

julia> @code_typed test_func(UInt(1)) 
LambdaInfo for test_func(::UInt64) 
:(begin 
     return (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.and_int)((Base.sle_int)(0,0)::Bool,((Base.box)(UInt64,(Base.and_int)(x,Main.COND)) === (Base.box)(UInt64,0))::Bool)))) 
    end::Bool) 

julia> @code_typed test_macro(UInt(1)) 
LambdaInfo for test_macro(::UInt64) 
:(begin 
     return (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.and_int)((Base.sle_int)(0,0)::Bool,((Base.box)(UInt64,(Base.and_int)(x,Main.COND)) === (Base.box)(UInt64,0))::Bool)))) 
    end::Bool) 

看看那個!內部表示中的這一步有點複雜,但您可以看到該函數已被內聯(即使沒有@inline!),現在代碼看起來完全相同。

我們可以走的更遠,並索要LLVM ......乃至兩個是完全相同:

julia> @code_llvm test_func(UInt(1))  | julia> @code_llvm test_macro(UInt(1)) 
              | 
define i8 @julia_test_func_70754(i64) #0 { | define i8 @julia_test_macro_70752(i64) #0 { 
top:          | top: 
    %1 = lshr i64 %0, 7      | %1 = lshr i64 %0, 7 
    %2 = xor i64 %1, 1      | %2 = xor i64 %1, 1 
    %3 = trunc i64 %2 to i8     | %3 = trunc i64 %2 to i8 
    %4 = and i8 %3, 1      | %4 = and i8 %3, 1 
    %5 = xor i8 %4, 1      | %5 = xor i8 %4, 1 
    ret i8 %5        | ret i8 %5 
}           | } 
+0

意想不到的答案,謝謝。代碼自省實用程序本身並不適用於宏,但我並沒有考慮將宏包裝在測試函數中。 問題:爲什麼註釋宏參數的類型是不好的? –

+2

這不一定是壞事,它只是沒有做你期望的。宏參數類型是指語法樹表示,而不是運行時間值的實際類型。例如'@foo(1)'可以派發到'macro foo(:: Int)',但是'x = 1; @foo(x)'調度到'foo(:: Symbol)'和'@foo(Int(1))'調度到'foo(:: Expr)'。這些區別通常不會有幫助,所以您幾乎從不想這樣做。 –