Julia - 嵌套循环消耗大量内存

try*_*lve 4 memory memory-management julia

我有一个嵌套的循环迭代方案,占用了大量的内存.我确实理解我不应该使用全局变量,但即使我将所有内容都包含在函数中,内存情况也没有改善.它只是在每次迭代后累积,就像没有垃圾收集一样.

这是一个可行的例子,类似于我的代码.

我有两个文件.首先,functions.jl:

##functions.jl
module functions

function getMatrix(A)
    L = rand(A,A);
    return L;
end

function loopOne(A, B)
    res = 0;
    for i = 1:B
        res = inv(getMatrix(A));
    end
    return(res);
end

end
Run Code Online (Sandbox Code Playgroud)

第二档main.jl:

##main.jl
include("functions.jl")
function main(C)
    res = 0;
    A = 50;
    B = 30;
    for i =1:C
        mat = functions.loopOne(A,B);
        res = mat .+ 1;
    end
    return res;
end
main(100)
Run Code Online (Sandbox Code Playgroud)

当我执行时julia main.jl,内存随着我的扩展C而增加main(C)(当我增加到C1000000 时,有时会增加到数百万个分配和10GiB ).

我知道这个例子看起来没用,但它类似于我的结构.有人可以帮忙吗?谢谢.

更新:

Michael K. Borregaard给出了一个非常有帮助的答案:

module Functions                            #1

function loopOne!(res, mymatrix, B)         #2
    for i = 1:B
        res .= inv(rand!(mymatrix))         #3
end
    return res                              #4
end

end

function some_descriptive_name(C)           #5
    A, B = 50, 30                           #6
    res, mymat = zeros(A,A), zeros(A,A) 
    for i =1:C
        res .= Functions.loopOne!(res, mymat, B) .+ 1
    end
    return res
Run Code Online (Sandbox Code Playgroud)

但是,当我计时时,拨号和内存仍会随着拨号而增加C.

@time some_descriptive_name(30)
  0.057177 seconds (11.77 k allocations: 58.278 MiB, 9.58% gc time)

@time some_descriptive_name(60)
  0.113808 seconds (23.53 k allocations: 116.518 MiB, 9.63% gc time)
Run Code Online (Sandbox Code Playgroud)

我相信这个问题来自于这个inv功能.如果我将代码更改为:

function some_descriptive_name(C)           #5
    A, B = 50, 30                           #6
    res, mymat = zeros(A,A), zeros(A,A) 
    for i =1:C
       res .= res .+ 1
    end
    return res
end
Run Code Online (Sandbox Code Playgroud)

然后内存和分配将保持不变:

@time some_descriptive_name(3)
  0.000007 seconds (8 allocations: 39.438 KiB)

@time some_descriptive_name(60)
  0.000037 seconds (8 allocations: 39.438 KiB)
Run Code Online (Sandbox Code Playgroud)

有没有办法在使用后"清除"内存inv?由于我没有创建任何新内容或存储任何新内容,因此内存使用量应保持不变.

Mic*_*ard 11

至少有几点建议:

  1. getMatrix函数每次都分配一个新的AxA矩阵.这肯定会消耗内存.如果可以,最好避免分配,例如通过使用rand!随机值填充现有数组.

  2. res = 0行定义res为a,Int但随后您将其分配Matrix{Float}给它(结果inv(getMatrix)).更改代码中变量的类型使编译器很难弄清楚类型是什么,这会导致代码变慢.

  3. 看来你有一个名为的模块,functions但是你没有写它.

  4. res = inv代码行不断改写值,因此该循环什么也不做!

  5. 结构和代码看起来像C++.试着看风格指南.

以下是代码以更加思想的方式看起来如何避免分配:

module Functions                            #1

function loopOne!(res, mymatrix, B)         #2
    for i = 1:B
        res .= inv(rand!(mymatrix))         #3
    end
    return res                              #4
end

end

function some_descriptive_name(C)           #5
    A, B = 50, 30                           #6
    res, mymat = zeros(A,A), zeros(A,A) 
    for i =1:C
        res .= Functions.loopOne!(res, mymat, B) .+ 1
    end
    return res
end
Run Code Online (Sandbox Code Playgroud)

评论:

  1. 如果您愿意,可以使用模块 - 由您决定是否将内容放在不同的文件中.模块名称大写.

  2. 如果可以,使用覆盖现有容器值的函数是有利的.这些函数最终!表示它们将修改参数(比如通过引用可变地传递,而不是在C++中使它成为const).

  3. 使用.=运算符表示您没有创建新容器,而是覆盖现有容器的元素.该rand!功能会被覆盖mymatrix.

  4. 不严格需要return关键字,但DNF建议在评论中使用更好的样式

  5. main约定未在Julia中使用,因为大多数代码由用户调用,而不是由程序执行.

  6. 多个变量的紧凑分配格式.

请注意,在这种情况下,这些优化都不重要,因为99%的计算时间花费在昂贵的inv功能上.

对更新的反应:inv函数没有任何问题,它只是一个昂贵的操作.但我想你可能会误解记忆计数的作用.并不是说内存使用量正在增加,就像你在C++中寻找的那样,如果你有一个指向永不释放的对象的指针(内存泄漏).内存使用是不变的,但分配的总和会增加,因为inv函数必须进行一些内部分配.

考虑这个例子

for i in 1:n
    b = [1, 2, 3, 4]  # Here a length-4 Array{Int64} is initialized in memory, cost is 32 bytes
end                   # Here, that memory is released.
Run Code Online (Sandbox Code Playgroud)

对于每次通过for循环的运行,分配32个字节,并释放32个字节.当循环结束时,无论n如何,都将从该操作分配0个字节.但是Julia的内存跟踪只会增加分配 - 所以在运行代码后你会看到32*n字节的分配.julia这样做的原因是在RAM中分配空间是计算中最昂贵的操作之一 - 因此减少分配是加速代码的好方法.但你无法避免它.

因此,您的代码没有任何问题(以新格式) - 您看到的内存分配和时间只是执行大(昂贵)操作的结果.