Неизменяемые типы и характеристики
Мне интересно о неизменных типах и выступлениях в Джулии.
-
В этом случае делает сложный тип неизменным улучшающим производительность? В документации указано
В некоторых случаях они более эффективны. Типы, подобные сложному примеру выше, могут быть эффективно упакованы в массивы, а в некоторых случаях компилятор может полностью исключить неизменяемые объекты.
Я не очень понимаю вторую часть.
-
Существуют ли случаи, когда стабильная производительность с сокращенными характеристиками составного типа (за исключением случая, когда поле должно быть изменено ссылкой)? Я думал, что один пример может быть, когда объект неизменяемого типа используется повторно как аргумент, так как
Объект с неизменяемым типом передается (как в операторах присваивания, так и в вызовах функций) путем копирования, тогда как изменяемый тип передается по ссылке.
Однако я не могу найти разницы в простом примере:
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
медленнее с неизменяемым типом? Существуют ли случаи, когда использование неизменяемых типов делает код медленнее?
Ответы
Ответ 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)