Julia

时间:2016-02-29 17:16:45

标签: memory julia allocation

在将程序从Python翻译成Julia之后,我非常不满意:

  • 对于小/非常小的输入,Python更快
  • 对于中等输入,Julia更快(但不是那么多)
  • 对于大输入,Python更快

我认为原因是我不了解内存分配的工作原理(autodidact在这里,没有CS背景)。我会在这里发布我的代码但是它太长而且太具体了,除了我之外它对任何人都没有好处。因此我做了一些实验,现在我有一些问题。

考虑这个简单的script.jl

function main()
    @time begin
        a = [1,2,3]
    end
end
main()

当我跑步时,我得到:

$ julia script.jl
  0.000004 seconds (1 allocation: 96 bytes)

1。为什么是96字节?当我设置a = []时,我得到64个字节(为什么空数组的重量如此之多?)。 96字节 - 64字节= 32字节。但aArray{Int64,1}。 3 * 64位= 3 * 8字节= 24字节!= 32字节。

2。即使我设置a = [1,2,3,4],为什么我会得到96个字节?

3。为什么运行时会获得937.500 KB:

function main()
    @time begin
        for _ in 1:10000
            a = [1,2,3]
        end
    end
end
main()

而不是960.000 KB?

4。例如,为什么filter()效率低下?看看这个:

check(n::Int64) = n % 2 == 0

function main()
    @time begin
        for _ in 1:1000
            a = [1,2,3]
            b = []
            for x in a
                check(x) && push!(b,x)
            end
            a = b
        end
    end
end
main()
$ julia script.jl
  0.000177 seconds (3.00 k allocations: 203.125 KB)

代替:

check(n::Int64) = n % 2 == 0

function main()
    @time begin
        for _ in 1:1000
            a = [1,2,3]
            a = filter(check,a)
        end
    end
end
main()

$ julia script.jl
  0.002029 seconds (3.43 k allocations: 225.339 KB)

如果我使用匿名函数(x -> x % 2 == 0)而不是检查内部过滤器,我得到:

$ julia script.jl
  0.004057 seconds (3.05 k allocations: 206.555 KB)

如果内置函数速度较慢且需要更多内存,为什么还要使用内置函数?

2 个答案:

答案 0 :(得分:11)

快速回答:

1。 Array会在标题中跟踪其维度和大小。

2。 Julia确保其数组为16-byte aligned。如果你看几个例子的分配,这个模式就变得很明显了:

julia> [@allocated(Array{Int64}(i)) for i=0:8]'
1x9 Array{Any,2}:
 64  80  80  96  96  112  112  128  128

3。它以千字节为单位进行报告。一千字节中有1024个字节:

julia> 937.500 * 1024
960000.0

4。匿名函数和将函数传递给更高阶函数(如filter)已知性能为0.4,并已在最新的开发版本中修复。

一般来说,获得比预期更多的分配通常是类型不稳定的标志。我强烈建议您阅读手册Performance Tips page以获取有关此内容的更多信息。

答案 1 :(得分:11)

很难弄清楚为什么你的代码在没有任何相关知识的情况下很慢,但是如果你愿意,你可以将它发布到julia-users - 很多人(包括我自己)很乐意提供帮助一些性能分析和调整。一旦掌握了它,Julia就有了一个非常简单的性能模型,但确实需要一点时间才能获得它。一旦你这样做,通常可以获得类似C的性能。以下是您具体问题的一些答案。

  
      
  1. 为什么要96个字节?为什么空阵列的重量如此之大?

  2.   
  3. 即使我设置a = [1,2,3,4],为什么我会得到96个字节?

  4.   

在动态语言中,数组是运行时对象,有关它们的元数据需要一些空间。您需要存储对象的类型标记,维度的数量和大小以及内存管理的标志。这在动态语言中非常标准 - IIRC,在PHP中,每个数组都有大约400字节的开销,但PHP“数组”实际上要远远超过它。在数组对象开销方面,Python和Ruby可能与Julia非常相似。

此外,Julia中的一维数组可以通过push!pop!以及其他类似的操作进行动态调整,并且在某种程度上可以进行过度调整以使这些操作更有效。当您通过逐个推送元素来增长矢量时,您会定期需要更多内存。为了提高效率,Julia预先分配了额外的空间。因此,单元素和双元素阵列具有相同的存储大小;三元素和四元素数组也是如此。对于中等大小的阵列,这种开销可以忽略不计。如果你需要存储很多小型数组,当然它可能会成为一个问题。有多种方法可以解决这个问题,但它似乎并不是你的问题。

  
      
  1. 为什么我会获得937.500 KB
  2.   

1 KB = 1024字节,因此937.5 KB * 1024字节/ KB = 960000字节。

  
      
  1. 例如,为什么filter()效率低下?
  2.   

如果您使用Julia的开发版本,这是有效的。这需要对功能如何实现以及它们如何与类型系统进行交互进行大规模改革,这是由Jeff Bezanson完成的。这是现在的表现:

julia> check(n) = n % 2 == 0
check (generic function with 1 method)

julia> function f1()
           for _ in 1:1000
               a = [1,2,3]
               b = []
               for x in a
                   check(x) && push!(b,x)
               end
               a = b
           end
       end
f1 (generic function with 1 method)

julia> function f2()
           for _ in 1:1000
               a = [1,2,3]
               a = filter(x -> x % 2 == 0, a)
           end
       end
f2 (generic function with 1 method)

julia> @time f1() # compilation
  0.013673 seconds (16.86 k allocations: 833.856 KB)

julia> @time f1()
  0.000159 seconds (3.00 k allocations: 203.281 KB)

julia> @time f2() # compilation
  0.012211 seconds (7.79 k allocations: 449.308 KB)

julia> @time f2()
  0.000159 seconds (3.00 k allocations: 203.281 KB)

现在表现难以区分。这仅适用于Julia master的最新版本,而不是0.4稳定版本,因此,如果您使用的是稳定版本,为了获得最佳性能,您需要自己编写过滤操作。