不可改变的类型和表演

时间:2015-08-02 18:49:38

标签: julia

我想知道朱莉娅的不可变类型和表演。

  1. 在哪种情况下,使复合类型不可变提高性能?文档说

      

    在某些情况下,它们更有效。像Complex这样的类型   以上可以有效地打包到数组中,在某些情况下可以打包   编译器能够避免完全分配不可变对象。

    我不太了解第二部分。

  2. 是否存在使复合类型不可变的情况降低性能(超出需要通过引用更改字段的情况)?我想一个例子可能是当一个不可变类型的对象被重复用作参数时,因为

      

    具有不可变类型的对象通过复制传递(在赋值语句和函数调用中),而可变类型通过引用传递。

    但是,我在一个简单的例子中找不到任何区别:

    abstract MyType
    
    type MyType1 <: MyType
        v::Vector{Int}
    end
    
    immutable MyType2 <: MyType
        v::Vector{Int}
    end
    
    
    g(x::MyType) = sum(x.v)
    
    function f(x::MyType)
        a = zero(Int)
        for i in 1:10_000
            a += g(x)
        end
        return a
    end
    
    x = fill(one(Int), 10_000)
    x1 = MyType1(x)
    @time f(x1)
    # elapsed time: 0.030698826 seconds (96 bytes allocated)
    x2 = MyType2(x)
    @time f(x2)
    # elapsed time: 0.031835494 seconds (96 bytes allocated)
    

    那么为什么使用不可变类型会导致f变慢?是否存在使用不可变类型使代码变慢的情况?

2 个答案:

答案 0 :(得分:8)

不可变类型在它们很小时特别快,并且完全由立即数据组成,没有对堆分配对象的引用(指针)。例如,由两个Int组成的不可变类型可能存储在寄存器中,根本不存在于内存中。

知道值不会发生变化也有助于我们优化代码。例如,您在循环内访问x.v,并且由于x.v将始终引用相同的向量,我们可以在循环外提升它的负载,而不是在每次迭代时重新加载。然而,您是否从中获得任何好处取决于该负载是否占用了循环中的大部分时间。

在实践中很少有不可变的代码减慢代码,但有两种情况可能会发生。首先,如果你有一个很大的不可变类型(例如100 Int s)并做一些事情,比如在需要多次移动它们的地方对它们的数组进行排序,那么额外的复制可能比指向带有引用的对象更慢。其次,不可变对象通常最初不在堆上分配。如果需要将堆引用存储到一个(例如在Any数组中),我们需要将对象移动到堆中。从那里,编译器通常不够智能,无法重用对象的堆分配版本,因此可能会重复复制它。在这种情况下,预先堆积分配单个可变对象会更快。

答案 1 :(得分:2)

此测试包含特殊情况,因此不可扩展且无法拒绝更好的不可变类型的性能 检查以下测试并查看不同的分配时间,当创建不可变的向量与可变的向量相比时

abstract MyType
type MyType1 <: MyType
    i::Int
    b::Bool
    f::Float64
end
immutable MyType2 <: MyType
    i::Int
    b::Bool
    f::Float64
end

@time x=[MyType2(i,1,1) for i=1:100_000];
# => 0.001396 seconds (2 allocations: 1.526 MB)
@time x=[MyType1(i,1,1) for i=1:100_000];
# => 0.003683 seconds (100.00 k allocations: 3.433 MB)