Julia中的@code_native、@code_typed和@code_llvm有什么区别?

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

在讨论julia时,我希望有一个类似于python的
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)
的调用已替换为literal
Int64
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