Julia中的内存分配

Pig*_*gna 6 memory allocation julia

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

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

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

考虑这个简单script.jl:

function main()
    @time begin
        a = [1,2,3]
    end
end
main()
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我得到:

$ julia script.jl
  0.000004 seconds (1 allocation: 96 bytes)
Run Code Online (Sandbox Code Playgroud)

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

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

3.运行时为什么我得到937.500 KB:

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

而不是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)
Run Code Online (Sandbox Code Playgroud)

代替:

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)
Run Code Online (Sandbox Code Playgroud)

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

$ julia script.jl
  0.004057 seconds (3.05 k allocations: 206.555 KB)
Run Code Online (Sandbox Code Playgroud)

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

Mat*_* B. 11

快速回答:

1. Array在标题中跟踪它们的维度和大小.

朱莉娅确保其数组是16字节对齐的.如果您查看更多示例的分配,模式将变得明显:

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

它以千字节为单位报告.一千字节有1024个字节:

julia> 937.500 * 1024
960000.0
Run Code Online (Sandbox Code Playgroud)

4.匿名函数和传递函数到更高阶函数,如filter0.4中已知的性能问题,并已在最新的开发版本中修复.

通常,获得比预期更多的分配通常是类型不稳定的标志.我强烈建议您阅读手册的" 性能提示"页面,了解有关此内容的更多信息.

  • 尝试运行`main()`两次,`main(); 主()`.第一次`check`被调用它被编译,并且测试时间不足以使这个可以忽略不计. (3认同)
  • Julia很快,因为它能够为不同的参数*类型*生成专门的代码.当你编写一个像`f()= sum((2,3))`这样的普通函数调用时,Julia能够看到`sum`是一个*常量*,因此它不需要动态查找在运行时运行(看看`@code_llvm f()` - 它甚至可以预先计算结果!).但是当你把函数作为参数传递时(`g(fn)= fn((2,3))`),它不知道你传递的是什么函数......它可能是`sum`或`prod`或者任何`功能'.更糟糕的是:Julia无法推断它可能会返回什么类型,所以它也是一种类型的不稳定性. (3认同)

Ste*_*ski 11

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

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

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

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

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

  1. 为什么我得到937.500 KB

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

  1. 例如,为什么filter()这么低效呢?

如果您使用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)
Run Code Online (Sandbox Code Playgroud)

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