Julia - 许多分配以浏览结构中的数组

Julia - many allocation to browse an array in struct

我目前正在为 Julia 的奇怪行为而苦恼。 我正在浏览一个数组,无论该数组是否在结构内部,Julia 的行为都不一样。

在结构内部的数组的情况下,有许多分配似乎毫无意义。具体来说,分配的数量与数组的大小一样多。

下面是复制此问题的代码:

function test1()
    a = ones(Float32, 256)

    for i = 1:256
        a[i]
    end
end

struct X
    mat
end

function test2()
    a = X(ones(Float32, 256))

    for i = 1:256
        a.mat[i]
    end
end

function main()
    test1()
    test2()

    @time test1()
    @time test2()
end

main()

我得到的输出:

0.000002 seconds (1 allocation: 1.141 KiB)
0.000012 seconds (257 allocations: 5.141 KiB)

一开始以为是类型问题,后来也没强求,循环后类型也没什么不同

感谢您的帮助。

您需要在 struct 中指定 mat 的类型。否则,您使用 X 的函数将无法专门化和优化。

Fields with no type annotation default to Any, and can accordingly hold any type of value. https://docs.julialang.org/en/v1/manual/types/index.html#Composite-Types-1

将您的结构定义更改为

struct X
    mat::Vector{Float32}
end

将解决问题。 现在的结果是:

  0.000000 seconds (1 allocation: 1.141 KiB)
  0.000000 seconds (1 allocation: 1.141 KiB)

如果您在代码中更改一件事,您实际上可以通过 @code_warntype 宏看到效果。

for i = 1:256
    a.mat[i]
end

这部分并没有做太多。要查看 @code_warntype 的效果,请将旧代码中的这一行更改为

for i = 1:256
    a.mat[i] += 1.
end

@code_warntype 的结果将 Any 变成红色,您通常应该避免这种颜色。原因是 mat 的类型在编译时未知。

> @code_warntype test2() # your test2() with old X def
Body::Nothing
1 ─ %1  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float32,1}, svec(Any, Int64), :(:ccall), 2, Array{Float32,1}, 256, 256))::Array{Float32,1}
│   %2  = invoke Base.fill!(%1::Array{Float32,1}, 1.0f0::Float32)::Array{Float32,1}
└──       goto #7 if not true
2 ┄ %4  = φ (#1 => 1, #6 => %14)::Int64
│   %5  = φ (#1 => 1, #6 => %15)::Int64
│   %6  = (Base.getindex)(%2, %4)::Any <------ See here
│   %7  = (%6 + 1.0)::Any
│         (Base.setindex!)(%2, %7, %4)
│   %9  = (%5 === 256)::Bool
└──       goto #4 if not %9
3 ─       goto #5
4 ─ %12 = (Base.add_int)(%5, 1)::Int64
└──       goto #5
5 ┄ %14 = φ (#4 => %12)::Int64
│   %15 = φ (#4 => %12)::Int64
│   %16 = φ (#3 => true, #4 => false)::Bool
│   %17 = (Base.not_int)(%16)::Bool
└──       goto #7 if not %17
6 ─       goto #2
7 ┄       return

现在有了 X 的新定义,您将在 @code_warntype 的结果中看到推断出每种类型。

您可能想要使用 Parametric Types if you want X.mat to hold other types of Vectors or values. With parametric types, the compiler will still be able to optimize your functions since the type will be known during compilation. I would really recommend you to read the relevant manual entry for types and performance tips

  1. 永远不要在结构定义中使用抽象类型。在您的示例中,Julia 需要存储变量指针而不仅仅是值,因此会降低速度。 而是使用参数类型:
julia> struct X{T}
           mat::T
           end

julia> X{Float64}.(1:3)
5-element Array{X{Float64},1}:
 X{Float64}(1.0)
 X{Float64}(2.0)
 X{Float64}(3.0)
  1. 如果不确定,请考虑使用 @code_warntype 宏来查看 Julia 编译器在哪些地方无法正确识别类型。