Подтвердить что ты не робот

Неизменяемые типы и характеристики

Мне интересно о неизменных типах и выступлениях в Джулии.

  • В этом случае делает сложный тип неизменным улучшающим производительность? В документации указано

    В некоторых случаях они более эффективны. Типы, подобные сложному примеру выше, могут быть эффективно упакованы в массивы, а в некоторых случаях компилятор может полностью исключить неизменяемые объекты.

    Я не очень понимаю вторую часть.

  • Существуют ли случаи, когда стабильная производительность с сокращенными характеристиками составного типа (за исключением случая, когда поле должно быть изменено ссылкой)? Я думал, что один пример может быть, когда объект неизменяемого типа используется повторно как аргумент, так как

    Объект с неизменяемым типом передается (как в операторах присваивания, так и в вызовах функций) путем копирования, тогда как изменяемый тип передается по ссылке.

    Однако я не могу найти разницы в простом примере:

    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 медленнее с неизменяемым типом? Существуют ли случаи, когда использование неизменяемых типов делает код медленнее?

4b9b3361

Ответ 1

Неизменяемые типы особенно быстры, когда они малы и состоят полностью из непосредственных данных, без ссылок (указателей) на объекты, выделенные кучей. Например, неизменяемый тип, состоящий из двух Int, потенциально может храниться в регистрах и вообще не существует в памяти.

Знание того, что значение не изменится, также помогает нам оптимизировать код. Например, вы получаете доступ к x.v внутри цикла, и поскольку x.v всегда будет ссылаться на один и тот же вектор, мы можем поднять нагрузку за него за пределы цикла вместо повторной загрузки на каждой итерации. Однако независимо от того, получаете ли вы какую-либо выгоду от этого, зависит от того, занимает ли эта загрузка значительную долю времени в цикле.

Редко на практике для непреложных слов замедляется код, но есть два случая, когда это может произойти. Во-первых, если у вас есть большой неизменный тип (скажем, 100 Int s) и делайте что-то вроде сортировки массива из них, где вам нужно много перемещать их, дополнительное копирование может быть медленнее, чем указание на объекты со ссылками. Во-вторых, неизменяемые объекты обычно не выделяются в куче изначально. Если вам нужно сохранить ссылку на кучу на один (например, в массиве Any), нам нужно переместить объект в кучу. Оттуда компилятор часто недостаточно умен, чтобы повторно использовать выделенную кучей версию объекта, и поэтому может копировать его повторно. В таком случае было бы быстрее просто кучи - выделить один измененный объект спереди.

Ответ 2

Этот тест включает в себя особые случаи, поэтому он не расширяется и не может отказать в улучшении производительности неизменяемых типов.
проверьте следующий тест и посмотрите на разные времена распределения, когда создайте вектор неизменяемых сравнений с вектором mutables

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)