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
    给编译器一个额外的提示,说明更大的函数对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
现在我们可以开始向下移动链,看看是否有差异。第一步是“降低”-这只是将语法转换为有限的子集,使编译器的工作更轻松。您可以看到,在此阶段,宏已展开,但函数调用仍然保留:

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
}                                          | }