这更多的是关于 Julia 语言如何工作的问题,而不是技术问题。我发现了结构和元组解构,但我想知道有关内存分配的底层过程是什么。
我目前正在编写代码来运行涉及 ODE 求解的模拟。我将所有参数存储在一个元组中,该元组用作 ODE 函数
ODE_fun
的参数。我认为解构元组是使用元组元素子集的好方法,并且可以防止您在每次引用参数 tuple.a
时编写: a
。但它每次都会创建一个新变量吗?如果是,它看起来不如调用 tuple.a
最优,因为它会在 ODE 求解算法的每次迭代时创建一个新的 a
变量。这对于包含许多参数和向量的元组可能很重要。或者它是否创建了一种指向 tuple.a
的指针?有没有一种方法可以以最佳方式使用解构?不幸的是,文档相当不清楚......
如果
NamedTuple
argument
的类型在编译时已知,那么无论您执行 myfun(argument.a, argument.b)
还是 myfun(argument...)
都没有关系
由于元组具有固定类型,Julia 的编译器可以处理该类型(前提是类型不模糊)。
考虑这两个函数和一个
NamedTuple
mypars
:
function f(pars)
+(pars...)
end
function f2(pars)
+(pars.a, pars.b)
end
mypars = (;a=1, b=4)
可以清楚地看到两者都是非分配的:
julia> @btime f($mypars)
1.400 ns (0 allocations: 0 bytes)
julia> @btime f2($mypars)
1.400 ns (0 allocations: 0 bytes)
现在让我们看看编译过程。降低的代码明显不同
julia> @code_lowered f(mypars)
CodeInfo(
1 ─ %1 = Core._apply_iterate(Base.iterate, Main.:+, pars)
└── return %1
)
julia> @code_lowered f2(mypars)
CodeInfo(
1 ─ %1 = Base.getproperty(pars, :a)
│ %2 = Base.getproperty(pars, :b)
│ %3 = %1 + %2
└── return %3
)
但是,当推断类型编译器意识到这些基本上是相同的:
julia> @code_typed f(mypars)
CodeInfo(
1 ─ %1 = Core.getfield(pars, 1)::Int64
│ %2 = Core.getfield(pars, 2)::Int64
│ %3 = Base.add_int(%1, %2)::Int64
└── return %3
) => Int64
julia> @code_typed f2(mypars)
CodeInfo(
1 ─ %1 = Base.getfield(pars, :a)::Int64
│ %2 = Base.getfield(pars, :b)::Int64
│ %3 = Base.add_int(%1, %2)::Int64
└── return %3
) => Int64
这又意味着 LLVM 将获得相同的代码进行编译:
julia> @code_llvm f(mypars)
; @ REPL[1]:1 within `f`
; Function Attrs: uwtable
define i64 @julia_f_702([2 x i64]* nocapture noundef nonnull readonly align 8 dereferenceable(16) %0) #0 {
top:
; @ REPL[1]:2 within `f`
%1 = getelementptr inbounds [2 x i64], [2 x i64]* %0, i64 0, i64 0
%2 = getelementptr inbounds [2 x i64], [2 x i64]* %0, i64 0, i64 1
; ┌ @ int.jl:87 within `+`
%unbox = load i64, i64* %1, align 8
%unbox1 = load i64, i64* %2, align 8
%3 = add i64 %unbox1, %unbox
; └
ret i64 %3
}
julia> @code_llvm f2(mypars)
; @ REPL[2]:1 within `f2`
; Function Attrs: uwtable
define i64 @julia_f2_704([2 x i64]* nocapture noundef nonnull readonly align 8 dereferenceable(16) %0) #0 {
top:
; @ REPL[2]:2 within `f2`
; ┌ @ Base.jl:37 within `getproperty`
%1 = getelementptr inbounds [2 x i64], [2 x i64]* %0, i64 0, i64 0
%2 = getelementptr inbounds [2 x i64], [2 x i64]* %0, i64 0, i64 1
; └
; ┌ @ int.jl:87 within `+`
%unbox = load i64, i64* %1, align 8
%unbox1 = load i64, i64* %2, align 8
%3 = add i64 %unbox1, %unbox
; └
ret i64 %3
}
您可以运行
@code_native f(mypars)
和 @code_native f2(mypars)
来发现生成的二进制文件是相同的。