Julia 朱莉娅:@inline是如何工作的?何时使用函数与宏?
我想内联许多小函数,例如测试某些条件下的标志:Julia 朱莉娅:@inline是如何工作的?何时使用函数与宏?,julia,Julia,我想内联许多小函数,例如测试某些条件下的标志: const COND = UInt(1<<BITS_FOR_COND) function is_cond(flags::UInt) return flags & COND != 0 end 我的动机是我正在使用的C代码中有许多类似的宏函数: #define IS_COND(flags) ((flags) & COND) 我反复计时函数、宏、用@inline定义的函数以及表达式本身,但在多次运行中,没有一个函数
const COND = UInt(1<<BITS_FOR_COND)
function is_cond(flags::UInt)
return flags & COND != 0
end
我的动机是我正在使用的C代码中有许多类似的宏函数:
#define IS_COND(flags) ((flags) & COND)
我反复计时函数、宏、用@inline定义的函数以及表达式本身,但在多次运行中,没有一个函数始终比其他函数快。1)和3)中的函数调用生成的代码比4)中的表达式长得多,但我不知道如何比较2),因为@code\u 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)快。暂时忽略在这种情况下,性能差异可以忽略不计,什么技术可以产生最高效的代码?如果两种语法产生完全相同的生成代码,您是否应该选择其中一种是。在这种情况下,函数远远优于宏
- 宏是强大的,但它们很棘手。您的
定义中有三个错误(您不想在参数上添加类型注释,您需要在返回的表达式中插入@IS___(COND
,并且需要使用标志
来获得正确的表达式)esc
- 函数定义正如您所期望的那样工作
- 也许更重要的是,该函数的工作原理与其他人所期望的一样。宏可以做任何事情,因此,
sigil是一个很好的警告,“这里发生了一些超出正常Julia语法的事情。”不过,如果它的行为就像一个函数,那么不妨将它变成一个函数@
- 函数是Julia中的一级对象;您可以传递它们,并将它们与高阶函数一起使用,如
map
- Julia是基于内联函数构建的。它的性能取决于它!小函数通常甚至不需要
注释-它只需要自己完成。您可以使用@inline
给编译器一个额外的提示,说明更大的函数对inline特别重要……但通常Julia很擅长自己解决这个问题(就像这里)@inline
- 与宏相比,内联函数的回溯和调试效果更好
那么,现在,它们会产生相同的生成代码吗?朱莉娅最强大的一点就是你能够要求它做“中间工作” 首先,一些机构:
julia> const COND = UInt(1<<7)
is_cond(flags) = return flags & COND != 0
macro IS_COND(flags)
return :($(esc(flags)) & COND != 0) # careful!
end
现在我们可以开始向下移动链,看看是否有差异。第一步是“降低”-这只是将语法转换为有限的子集,使编译器的工作更轻松。您可以看到,在此阶段,宏已展开,但函数调用仍然保留:
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)
可以分派到宏foo(::Int)
,但是x=1@foo(x)
发送到foo(::Symbol)
和@foo(Int(1))
发送到foo(::Expr)
。这些区别通常是没有帮助的,所以这就是为什么你几乎不想这样做。
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)
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
} | }