调用存储在 Julia 变量中的函数时出现意外的内存分配

问题描述 投票:0回答:1

我在 Julia 中遇到一个问题,调用存储在结构中的函数会导致意外的内存分配,如下面的代码所示。直接函数调用(变体 1)不会导致任何分配,但函数存储在结构体或变量中的其他变体会导致 16 字节的分配。这会显着影响性能。为什么会发生这种情况,如何避免这些分配?

using BenchmarkTools

mutable struct Foo
  v::Float64
  f::Function
end

function add_one(x::Foo)
  x.v += 1
end

x = Foo(0.0, add_one)

println("variant 1")
@btime add_one(x)
@btime add_one(x)

println("variant 2")
@btime x.f(x)
@btime x.f(x)

println("variant 3")
func = x.f
@btime func(x)
@btime func(x)

println("variant 4")
func1 = add_one
@btime func1(x)
@btime func1(x)

println("variant 5")
func2 = (x::Foo) -> begin x.v += 1 end
@btime func2(x)
@btime func2(x)

println("func = '$func'")

输出:

variant 1
  6.900 ns (0 allocations: 0 bytes)
  6.900 ns (0 allocations: 0 bytes)
variant 2
  61.060 ns (1 allocation: 16 bytes)
  61.122 ns (1 allocation: 16 bytes)
variant 3
  28.414 ns (1 allocation: 16 bytes)
  28.543 ns (1 allocation: 16 bytes)
variant 4
  28.442 ns (1 allocation: 16 bytes)
  28.342 ns (1 allocation: 16 bytes)
variant 5
  27.711 ns (1 allocation: 16 bytes)
  27.739 ns (1 allocation: 16 bytes)
func = 'add_one'

这个问题是 Julia 设计固有的吗?为什么变体 2 明显慢于变体 3-5?

我打算广泛使用变体 2 和仅涉及几个操作的简单函数,这意味着我不能忽略开销。

function memory julia
1个回答
0
投票

你需要避免抽象类型:

mutable struct Foo2{T <: Function}
    v::Float64
    f::T
end

function add_one(x::Foo2)
    x.v += 1.0 # use the same type of one as the data or use `one(x.v)`
end

现在你可以做:

julia> y = Foo2(0.0, add_one)
Foo2{typeof(add_one)}(0.0, add_one)

julia> @btime $y.f($y)
  2.500 ns (0 allocations: 0 bytes)
500503.0

请注意,您需要在测试中插入

y
- 否则您正在测量 Julia 获得
y
类型的努力。

您还可以将其设为

const
(从而使基准测试的类型稳定):

julia> const z = Foo2(0.0, add_one);

julia> @btime z.f(z)
  2.500 ns (0 allocations: 0 bytes)
500503.0

最后但并非最不重要的一点是

@btime
只能运行一次。

© www.soinside.com 2019 - 2024. All rights reserved.