【问题标题】:Julia - Is there a way to avoid using a loop without using too much memoryJulia - 有没有办法在不使用太多内存的情况下避免使用循环
【发布时间】:2017-09-24 17:42:50
【问题描述】:

考虑以下示例:

module structs
mutable struct testStruct
    x::Array{Int64,2}
end
end

function innerFunc(s::structs.testStruct)
    s.x[1:2] .= s.x[3:4]
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerFunc(s)
    end
    println(s.x)
end

随着我增加n,内存分配也会增加。我认为这是因为在每次迭代中,我都会为s.x[3:4] 创建一个分配。我可以通过使用循环来避免这种情况:

function innerFunc(s::structs.testStruct)
    for i = 1:2
        s.x[i] .= s.x[i+2]
    end
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerFunc(s)
    end
    println(s.x)
end

但是,我不喜欢循环,因为语法很麻烦。有没有办法避免它?在每次迭代中,我想修改s.x 的第一个和第二个元素而不增加内存分配,因为我增加了n,因为我没有创建任何新内容。

更新:为了响应 DNF,我尝试使用@view

module structs
mutable struct testStruct
    x::Array{Int64,2}
end
end

function innerfunc!(s::structs.testStruct)
    s.x[1:2] .= view(s.x, 3:4)
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerfunc!(s)
    end
    println(s.x)
end

这是我得到的:

@time structTest!(structs.testStruct([1 2 3 4]),33)
0.000112 seconds (202 allocations: 7.938 KiB)

@time structTest!(structs.testStruct([1 2 3 4]),330)
0.000126 seconds (1.69 k allocations: 68.266 KiB)

我希望分配对n 保持不变。

【问题讨论】:

  • 我不确定您为什么认为要避免循环?这是直截了当的解决方案,您为什么认为它很麻烦?
  • 我不喜欢这种语法。使用矢量化操作(或类似矢量化的操作)使代码看起来更直观。
  • 视图当前分配了一点点,但这是将在 1.x 中解决的优化问题之一。一般来说,我不会担心它,因为它很小,而且迟早会消失。

标签: performance julia allocation


【解决方案1】:

正如@DNF 所说,使用视图应该可以解决问题,但是因为视图不是不可变的,所以内存开销很小,这在通常的应用程序中是无关紧要的。

所以本质上,您必须使用for 循环。为了让它看起来更矢量化,Dahua Lin 的 Devectorized.jl 包来拯救它:

# Pkg.add("Devectorized.jl")
using Devectorized

function innerfunc!(s::structs.testStruct)
    @devec s.x[1:2] = s.x[3:4]
end

你很高兴:

julia> @time structTest!(structs.testStruct([1 2 3 4]),3300)
  0.000299 seconds (40 allocations: 1.641 KiB)

julia> @time structTest!(structs.testStruct([1 2 3 4]),330000)
  0.001209 seconds (40 allocations: 1.641 KiB)

【讨论】:

  • 什么是“for[ce]”? @devec@view 好吗?
  • “原力”是对《星球大战》电影参考的一种格式蹩脚的尝试。 @devec@views 不同。视图是真正的 Julia 结构,可以通过多次调度来使用和改进。 @devec 是更多的语法糖,它将类似向量的表达式转换为 for 循环,因此受到更多限制。
  • 编辑了 StarWars 参考资料(“使用 force”)。认为 StarWars 已经嵌入到每个人的 DNA 中,但如果 StackOverflow 和 Julia 在 meme-memory 中持续更长时间,最好不要混淆未来的读者。
  • 哈哈我一开始确实得到了参考,但我知道你指的是for循环。
  • 另一个问题:当我使用BenchmarkTools 时,我得到了分配的数量和使用的内存(以 MiB 为单位):419.156 ms (72920 allocations: 272.37 MiB)。我测试了两个函数,但是一个使用的内存比另一个多(以 MiB 为单位),而它使用的分配更少。这可能吗?
【解决方案2】:

使用view

function innerfunc!(s::structs.testStruct)
    s.x[1:2] .= view(s.x, 3:4)
end

function innerfunc!(s::structs.testStruct)
    @views s.x[1:2] .= s.x[3:4]
end

哦,在函数名中加上 !,因为它会改变输入。

编辑:显然,我错了。视图 do 分配一点。但是你做基准测试的方式会给你非常错误的答案,特别是当你在全局范围内进行基准测试时,内存估计会偏离。一些提示:

  1. 使用 BenchmarkTools.jl 进行基准测试。
  2. 您不需要可变结构。您可以在不可变结构中更改数组。
  3. 狡辩,但使用x::Array{Int, 1}x::Vector{Int} 而不是Array{Int, 2}
  4. 除非您确实需要,否则不要在函数中添加 print 语句!

struct TestStruct
    x::Vector{Int64}
end

function innerfunc!(s::TestStruct)
    s.x[1:2] .= view(s.x, 3:4)
end

function structtest!(s::TestStruct, n)
    for i = 1:n
        innerfunc!(s)
    end
    return s
end

julia> s = TestStruct([1, 2, 3, 4])
TestStruct([1, 2, 3, 4])

julia> @btime structtest!($s, 33)
  575.108 ns (33 allocations: 1.55 KiB)
TestStruct([3, 4, 3, 4])

【讨论】:

  • 我试过了,但是内存分配对于n来说并不是不变的。
  • 分配应该为零。
  • 也许您可以提供一个最小的工作示例。您粘贴的代码实际上无法运行。
  • 谢谢。为什么在最后一行的s 之前需要$
  • 它将变量插入到表达式中。如果s 是全局非常量变量,则 jit 编译器在优化编译后的代码时会出现问题。 $s 拼接表达式中的值并将其置于基准范围内。您不必这样做,基准仍然会运行,但通常会给出太慢的结果。
猜你喜欢
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 2016-05-12
  • 2021-12-30
  • 2021-10-09
  • 1970-01-01
  • 1970-01-01
  • 2021-09-08
相关资源
最近更新 更多