Wenn die beiden Syntaxen genau denselben generierten Code ergeben, sollten Sie einen über den anderen bevorzugen? JA. Funktionen sind Makros in solchen Situationen weit überlegen.
- Makros sind leistungsstark, aber sie sind knifflig. Sie haben drei Fehler in Ihrer
@IS_COND
Definition (Sie möchten nicht eine Typ Annotation auf das Argument setzen, müssen Sie flags
in den zurückgegebenen Ausdruck interpolieren, und Sie müssen esc
verwenden, um die Hygiene richtig zu bekommen).
- Die Funktionsdefinition funktioniert so, wie Sie es erwarten.
- Vielleicht noch wichtiger ist, funktioniert die Funktion wie andere erwarten würde. Makros können alles, so dass
@
Sigil ist eine gute Warnung für "etwas über normale Julia-Syntax tritt hier." Wenn es sich jedoch genau wie eine Funktion verhält, könnte es auch eins machen.
- Funktionen sind erstklassige Objekte in Julia; Sie können sie weitergeben und sie mit Funktionen höherer Ordnung wie
map
verwenden.
- Julia ist auf Inlinefunktionen aufgebaut. Seine Leistung hängt davon ab! Kleine Funktionen benötigen normalerweise nicht einmal die
@inline
Annotation - sie macht es nur für sich. Sie können @inline
verwenden, um dem Compiler einen zusätzlichen Hinweis zu geben, dass eine größere Funktion besonders wichtig für Inline ist ... aber oft ist Julia gut darin, es selbst herauszufinden (wie hier).
- Backtraces und Debugging funktionieren besser mit eingebetteten Funktionen als Makros.
So, jetzt, nicht zur Folge haben sie in dem gleichen generierten Code? Eines der mächtigsten Dinge an Julia ist deine Fähigkeit, sie um ihre "Zwischenarbeit" zu bitten.
Zuerst etwas ein:
julia> const COND = UInt(1<<7)
is_cond(flags) = return flags & COND != 0
macro IS_COND(flags)
return :($(esc(flags)) & COND != 0) # careful!
end
Jetzt können wir beginnen bei der Suche, was passiert, wenn Sie entweder is_cond
oder @IS_COND
verwenden. Im eigentlichen Code, werden Sie diese Definitionen in anderen Funktionen verwenden möchten, so lassen Sie uns einige Testfunktionen erstellen:
julia> test_func(x) = is_cond(x)
test_macro(x) = @IS_COND(x)
Jetzt können wir die Kette nach unten bewegen beginnen, um zu sehen, ob es einen Unterschied gibt. Der erste Schritt ist das "Senken" - dies wandelt die Syntax einfach in eine begrenzte Teilmenge um, um dem Compiler das Leben zu erleichtern. Sie können, dass in diesem Stadium sehen, wird das Makro erweitert, aber der Funktionsaufruf bleibt noch:
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)
Der nächste Schritt ist jedoch, Inferenz und Optimierung. Es ist hier, dass Funktion Inlining wirksam wird:
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)
Schau dir das an! Dieser Schritt in der internen Darstellung ist ein wenig unordentlicher, aber Sie können sehen, dass die Funktion inline wurde (auch ohne @inline
!) Und jetzt sieht der Code genau identisch zwischen den beiden aus.
Wir gehen weiter und fragen Sie nach dem LLVM ... und in der Tat die beiden sind genau identisch:
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
} | }
Fantastische Antwort, danke. Code-Introspection-Dienstprogramme arbeiteten nicht an dem Makro selbst, aber ich habe nicht erwogen, das Makro in einer Testfunktion zu umbrechen. Frage: Warum ist es schlecht, Arten von Makroargumenten zu kommentieren? –
Es ist nicht unbedingt schlecht, es macht einfach nicht das, was du erwartest. Makroargumenttypen beziehen sich auf die Syntaxbaumdarstellung und nicht auf die tatsächlichen Arten von Laufzeitwerten. Zum Beispiel könnte "@foo (1)" an "Makro foo (:: Int)" senden, aber "x = 1; @foo (x) 'sendet an' foo (:: Symbol) 'und' @foo (Int (1)) 'sendet an' foo (:: Expr) '. Diese Unterscheidungen sind normalerweise nicht hilfreich, deshalb möchten Sie das fast nie tun. –