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

时间:2017-09-20 19:16:23

标签: 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

第二个文件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)

当我执行julia main.jl时,随着Cmain(C)的扩展,内存会增加(当我将C增加到1000000时,有时会超过数百万次分配和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

然而,当我计时时,拨号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)

我认为问题来自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

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

@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)

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

1 个答案:

答案 0 :(得分:11)

至少有几点指示:

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

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

  3. 您似乎有一个名为functions的模块,但您不会写它。

  4. res = inv代码行不断覆盖该值,因此循环不执行任何操作!

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

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

    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
    

    评论:

    1. 如果您愿意,可以使用模块 - 您是否将文件放在不同的文件中取决于您自己。模块名称大写。

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

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

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

    5. 在Julia中没有使用main约定,因为大多数代码都是由用户调用的,而不是通过执行程序调用的。

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

    7. 请注意,在这种情况下,这些优化都不重要,因为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.
      

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

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