Julia中的@code_native、@code_typed和@code_llvm有什么区别?
在讨论julia时,我希望有一个类似于python的Julia中的@code_native、@code_typed和@code_llvm有什么区别?,julia,Julia,在讨论julia时,我希望有一个类似于python的dis模块的功能。 通过网络浏览,我发现Julia社区已经研究了这个问题,并给出了这些() 我曾亲自使用Julia REPL尝试过这些,但我觉得很难理解 在Python中,我可以反汇编这样的函数 >>> import dis >>> dis.dis(lambda x: 2*x) 1 0 LOAD_CONST 1 (2) 3 LOA
dis
模块的功能。
通过网络浏览,我发现Julia社区已经研究了这个问题,并给出了这些()
我曾亲自使用Julia REPL尝试过这些,但我觉得很难理解
在Python中,我可以反汇编这样的函数
>>> import dis
>>> dis.dis(lambda x: 2*x)
1 0 LOAD_CONST 1 (2)
3 LOAD_FAST 0 (x)
6 BINARY_MULTIPLY
7 RETURN_VALUE
>>>
有人能帮助我更好地理解这些吗?谢谢。Python的标准CPython实现解析源代码,并对其进行一些预处理和简化(也称为“降低”),将其转换为一种机器友好、易于解释的格式,称为“”。这是“反汇编”Python函数时显示的内容。此代码不可由硬件执行–它可由CPython解释器“执行”。CPython的字节码格式相当简单,部分原因是解释器往往擅长于此——如果字节码太复杂,它会减慢解释器的速度——部分原因是Python社区倾向于高度重视简单性,有时会以高性能为代价 Julia的实现不是解释的,而是解释的。这意味着当您调用函数时,它将转换为机器代码,由本机硬件直接执行。这个过程比Python解析和降低字节码的过程要复杂得多,但作为交换,Julia获得了其标志性的速度。(PyPy-JIT for Python也比CPython复杂得多,但通常也要快得多——增加复杂性是提高速度的典型代价。)“反汇编”的四个级别for Julia code允许您在从源代码到机器代码的转换的不同阶段访问特定参数类型的Julia方法实现的表示。我将使用以下函数计算参数后的下一个斐波那契数作为示例:
function nextfib(n)
a, b = one(n), one(n)
while b < n
a, b = b, a + b
end
return b
end
julia> nextfib(5)
5
julia> nextfib(6)
8
julia> nextfib(123)
144
您可以看到SSAValue
节点和,除非
/goto
构造和标签编号。这并不是很难阅读,但再一次,它也不是真的意味着人类消费容易。降低的代码不依赖于参数的类型,除非它们决定调用哪个方法体——只要调用相同的方法,就应用相同的降低的代码
类型化代码。宏@code\u Typed
在和之后为一组特定的参数类型提供方法实现。代码的这种体现形式类似于简化形式,但是使用类型信息注释表达式,并用它们的实现替换一些通用函数调用。例如,下面是示例函数的类型代码:
julia> @code_typed nextfib(123)
CodeInfo(:(begin
a = 1
b = 1 # line 3:
4:
unless (Base.slt_int)(b, n)::Bool goto 13 # line 4:
SSAValue(2) = b
SSAValue(3) = (Base.add_int)(a, b)::Int64
a = SSAValue(2)
b = SSAValue(3)
11:
goto 4
13: # line 6:
return b
end))=>Int64
对one(n)
的调用已替换为literalInt64
值1
(在我的系统上,默认的整数类型是Int64
)。表达式b
已替换为其在slt_int
(“小于的有符号整数”)方面的实现,其结果已用返回类型Bool
注释。表达式a+b
也被替换为add_int
内在函数的实现,其结果类型被注释为Int64
。整个函数体的返回类型被注释为Int64
降低的代码仅依赖于参数类型来确定调用哪个方法体,与此不同,类型化代码的详细信息取决于参数类型:
julia> @code_typed nextfib(Int128(123))
CodeInfo(:(begin
SSAValue(0) = (Base.sext_int)(Int128, 1)::Int128
SSAValue(1) = (Base.sext_int)(Int128, 1)::Int128
a = SSAValue(0)
b = SSAValue(1) # line 3:
6:
unless (Base.slt_int)(b, n)::Bool goto 15 # line 4:
SSAValue(2) = b
SSAValue(3) = (Base.add_int)(a, b)::Int128
a = SSAValue(2)
b = SSAValue(3)
13:
goto 6
15: # line 6:
return b
end))=>Int128
这是Int128
参数的nextfib
函数的类型化版本。文本1
必须符号扩展到Int128
,并且操作的结果类型是Int128
类型,而不是Int64
。如果类型的实现有很大的不同,那么类型化代码可能会有很大的不同。例如,BigInts
的nextfib
比像Int64
和Int128
这样的简单“位类型”要复杂得多:
julia> @code_typed nextfib(big(123))
CodeInfo(:(begin
$(Expr(:inbounds, false))
# meta: location number.jl one 164
# meta: location number.jl one 163
# meta: location gmp.jl convert 111
z@_5 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 112:
$(Expr(:foreigncall, (:__gmpz_set_si, :libgmp), Void, svec(Ptr{BigInt}, Int64), :(&z@_5), :(z@_5), 1, 0))
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
$(Expr(:inbounds, false))
# meta: location number.jl one 164
# meta: location number.jl one 163
# meta: location gmp.jl convert 111
z@_6 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 112:
$(Expr(:foreigncall, (:__gmpz_set_si, :libgmp), Void, svec(Ptr{BigInt}, Int64), :(&z@_6), :(z@_6), 1, 0))
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
a = z@_5
b = z@_6 # line 3:
26:
$(Expr(:inbounds, false))
# meta: location gmp.jl < 516
SSAValue(10) = $(Expr(:foreigncall, (:__gmpz_cmp, :libgmp), Int32, svec(Ptr{BigInt}, Ptr{BigInt}), :(&b), :(b), :(&n), :(n)))
# meta: pop location
$(Expr(:inbounds, :pop))
unless (Base.slt_int)((Base.sext_int)(Int64, SSAValue(10))::Int64, 0)::Bool goto 46 # line 4:
SSAValue(2) = b
$(Expr(:inbounds, false))
# meta: location gmp.jl + 258
z@_7 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 259:
$(Expr(:foreigncall, ("__gmpz_add", :libgmp), Void, svec(Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), :(&z@_7), :(z@_7), :(&a), :(a), :(&b), :(b)))
# meta: pop location
$(Expr(:inbounds, :pop))
a = SSAValue(2)
b = z@_7
44:
goto 26
46: # line 6:
return b
end))=>BigInt
这是nextfib(123)
方法实现的内存中LLVM IR的文本形式。LLVM不容易阅读——大多数情况下,它不是供人们编写或阅读的——但它是彻底的。一旦你掌握了窍门,就不难理解了。此代码跳转到标签L4
,并使用i64
(LLVM的名称表示Int64
)值1
初始化“寄存器”%storemerge1
和%storemerge
(它们的值在从不同位置跳转时派生不同–这就是phi
指令所做的). 然后执行icmp slt
将%storemerge
与寄存器%0
进行比较,在整个方法执行过程中保持参数不变,并将比较结果保存到寄存器%1
。它在%storemerge
和%storemerge1
上执行添加i64
,并将结果保存到寄存器%2
。如果%1
为true,它将分支回L4
,否则将分支到L13
。当代码循环回到L4
时,寄存器%storemerge1
获取%storemerge
的先前值,%storemerge
获取%2
的先前值
本机代码。由于Julia执行本机代码,因此方法实现采用的最后一种形式是机器实际执行的形式。这只是内存中的二进制代码,很难读取,所以很久以前人们发明了各种形式的“汇编语言”,用名称表示指令和寄存器,并有一些简单的syn
julia> @code_typed nextfib(Int128(123))
CodeInfo(:(begin
SSAValue(0) = (Base.sext_int)(Int128, 1)::Int128
SSAValue(1) = (Base.sext_int)(Int128, 1)::Int128
a = SSAValue(0)
b = SSAValue(1) # line 3:
6:
unless (Base.slt_int)(b, n)::Bool goto 15 # line 4:
SSAValue(2) = b
SSAValue(3) = (Base.add_int)(a, b)::Int128
a = SSAValue(2)
b = SSAValue(3)
13:
goto 6
15: # line 6:
return b
end))=>Int128
julia> @code_typed nextfib(big(123))
CodeInfo(:(begin
$(Expr(:inbounds, false))
# meta: location number.jl one 164
# meta: location number.jl one 163
# meta: location gmp.jl convert 111
z@_5 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 112:
$(Expr(:foreigncall, (:__gmpz_set_si, :libgmp), Void, svec(Ptr{BigInt}, Int64), :(&z@_5), :(z@_5), 1, 0))
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
$(Expr(:inbounds, false))
# meta: location number.jl one 164
# meta: location number.jl one 163
# meta: location gmp.jl convert 111
z@_6 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 112:
$(Expr(:foreigncall, (:__gmpz_set_si, :libgmp), Void, svec(Ptr{BigInt}, Int64), :(&z@_6), :(z@_6), 1, 0))
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
a = z@_5
b = z@_6 # line 3:
26:
$(Expr(:inbounds, false))
# meta: location gmp.jl < 516
SSAValue(10) = $(Expr(:foreigncall, (:__gmpz_cmp, :libgmp), Int32, svec(Ptr{BigInt}, Ptr{BigInt}), :(&b), :(b), :(&n), :(n)))
# meta: pop location
$(Expr(:inbounds, :pop))
unless (Base.slt_int)((Base.sext_int)(Int64, SSAValue(10))::Int64, 0)::Bool goto 46 # line 4:
SSAValue(2) = b
$(Expr(:inbounds, false))
# meta: location gmp.jl + 258
z@_7 = $(Expr(:invoke, MethodInstance for BigInt(), :(Base.GMP.BigInt))) # line 259:
$(Expr(:foreigncall, ("__gmpz_add", :libgmp), Void, svec(Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), :(&z@_7), :(z@_7), :(&a), :(a), :(&b), :(b)))
# meta: pop location
$(Expr(:inbounds, :pop))
a = SSAValue(2)
b = z@_7
44:
goto 26
46: # line 6:
return b
end))=>BigInt
julia> @code_llvm nextfib(123)
define i64 @julia_nextfib_60009(i64) #0 !dbg !5 {
top:
br label %L4
L4: ; preds = %L4, %top
%storemerge1 = phi i64 [ 1, %top ], [ %storemerge, %L4 ]
%storemerge = phi i64 [ 1, %top ], [ %2, %L4 ]
%1 = icmp slt i64 %storemerge, %0
%2 = add i64 %storemerge, %storemerge1
br i1 %1, label %L4, label %L13
L13: ; preds = %L4
ret i64 %storemerge
}
julia> @code_native nextfib(123)
.section __TEXT,__text,regular,pure_instructions
Filename: REPL[1]
pushq %rbp
movq %rsp, %rbp
movl $1, %ecx
movl $1, %edx
nop
L16:
movq %rdx, %rax
Source line: 4
movq %rcx, %rdx
addq %rax, %rdx
movq %rax, %rcx
Source line: 3
cmpq %rdi, %rax
jl L16
Source line: 6
popq %rbp
retq
nopw %cs:(%rax,%rax)
movl $1, %ecx
movl $1, %edx
nop
L16:
movq %rdx, %rax
Source line: 4
movq %rcx, %rdx
addq %rax, %rdx
movq %rax, %rcx
Source line: 3
cmpq %rdi, %rax
jl L16