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 Vector
s 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。
- 永远不要在结构定义中使用抽象类型。在您的示例中,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)
- 如果不确定,请考虑使用
@code_warntype
宏来查看 Julia 编译器在哪些地方无法正确识别类型。
我目前正在为 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 Vector
s 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。
- 永远不要在结构定义中使用抽象类型。在您的示例中,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)
- 如果不确定,请考虑使用
@code_warntype
宏来查看 Julia 编译器在哪些地方无法正确识别类型。