如果這兩種語法導致完全相同的生成代碼,你應該比另一種更適合嗎? 是。在這種情況下,函數遠遠優於宏。
- 宏很強大,但它們很棘手。您的
@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
} | }
意想不到的答案,謝謝。代碼自省實用程序本身並不適用於宏,但我並沒有考慮將宏包裝在測試函數中。 問題:爲什麼註釋宏參數的類型是不好的? –
這不一定是壞事,它只是沒有做你期望的。宏參數類型是指語法樹表示,而不是運行時間值的實際類型。例如'@foo(1)'可以派發到'macro foo(:: Int)',但是'x = 1; @foo(x)'調度到'foo(:: Symbol)'和'@foo(Int(1))'調度到'foo(:: Expr)'。這些區別通常不會有幫助,所以您幾乎從不想這樣做。 –