我想要内联很多小函数,例如测试某些条件的标志:
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)更快是不明显的。暂时忽略在这种情况下性能差异可以忽略不计,哪种技术可以产生最有效的代码?
答案 0 :(得分:21)
如果两个语法产生完全相同的生成代码,您是否应该优先选择另一个? 是即可。在这种情况下,函数远远优于宏。
@IS_COND
定义中有三个错误(你不想在参数上放置一个类型注释,你需要在返回的表达式中插入flags
,然后你需要使用esc
来确保卫生正确。@
sigil是一个很好的警告,“这里发生了一些超出正常的Julia语法。”但是,如果它的行为就像一个函数一样,那么它也可以成为一个函数。map
。@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
现在,我们可以开始查看使用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
} | }