#vector #multidimensional-array #julia
Вопрос:
Я хочу умножить несколько матриц (все одинакового размера) на векторную бета. Я пробовал две версии для хранения матриц, либо в виде вектора матриц, либо в виде массива трех измерений.
Нормально ли, что версия с вектором матриц работает быстрее?
using BenchmarkTools
nid =10000
npar = 5
x1 = reshape(repeat([1], nid * npar), nid,:)
x2 = reshape(repeat([2], nid * npar), nid,:)
x3 = reshape(repeat([3], nid * npar), nid,:)
X = reshape([x1 x2 x3], nid, npar, :);
X1 = [x1, x2, x3]
beta = rand(npar)
function f(X::Array{Int,3}, beta::Vector{Float64})::Array{Float64}
hcat([ X[:,:,i] * beta for i=1:size(X,3)]...)
end
function g(X::Array{Array{Int,2},1}, beta::Vector{Float64})::Array{Float64}
hcat([X[i] * beta for i=1:size(X)[1]]...)
end
f(X,beta);
g(X1,beta);
@benchmark f(X, beta)
@benchmark g(X1, beta)
Результаты показывают, что f занимает почти 2 раза больше времени g.
Это нормальный шаблон, или я неправильно использую 3D-массив?
Ответ №1:
Это связано с тем, что оператор среза заставляет копировать каждую матрицу, что приводит к увеличению выделения памяти.
Обратите внимание на последнюю строку контрольного показателя:
julia> @benchmark f(X, beta)
BenchmarkTools.Trial: 8011 samples with 1 evaluation.
Range (min … max): 356.013 μs … 4.076 ms ┊ GC (min … max): 0.00% … 87.21%
Time (median): 457.231 μs ┊ GC (median): 0.00%
Time (mean ± σ): 615.235 μs ± 351.236 μs ┊ GC (mean ± σ): 6.54% ± 11.69%
▃▇██▆▅▄▄▄▃▂ ▂▃▄▄▄▃▁▁▁ ▁ ▁ ▂
████████████▆▇▅▅▄▄▇███████████████▇█▇▇█▇▆▇█▆▆▆▆▅▆▅▅▄▃▃▂▂▄▂▄▄▄ █
356 μs Histogram: log(frequency) by time 1.96 ms <
Memory estimate: 1.60 MiB, allocs estimate: 17.
julia> @benchmark g(X1, beta)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 174.348 μs … 2.493 ms ┊ GC (min … max): 0.00% … 83.85%
Time (median): 219.383 μs ┊ GC (median): 0.00%
Time (mean ± σ): 245.192 μs ± 119.612 μs ┊ GC (mean ± σ): 3.54% ± 7.68%
▃▇█▂
▅████▆▄▄▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
174 μs Histogram: frequency by time 974 μs <
Memory estimate: 469.25 KiB, allocs estimate: 11.
Чтобы избежать этого, просто используйте макрос @views
, который вызывает ссылку, без выделения. Затем время между двумя реализациями становится одинаковым из-за случайных шумов:
function fbis(X::Array{Int,3}, beta::Vector{Float64})::Array{Float64}
@views hcat([ X[:,:,i] * beta for i=1:size(X,3)]...)
end
julia> @benchmark fbis(X, beta)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 175.984 μs … 2.710 ms ┊ GC (min … max): 0.00% … 79.70%
Time (median): 225.990 μs ┊ GC (median): 0.00%
Time (mean ± σ): 274.611 μs ± 166.015 μs ┊ GC (mean ± σ): 4.17% ± 7.78%
▅█▃
▆███▆▄▃▄▄▃▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
176 μs Histogram: frequency by time 1.15 ms <
Memory estimate: 469.25 KiB, allocs estimate: 11.
Хотя в этом случае использование ссылок улучшает контрольные показатели, обратите внимание, чтобы не злоупотреблять ими. Если вы «создаете» некоторую матрицу, которую собираетесь использовать снова и снова, начальное время выделения при копировании матрицы может стать незначительным по сравнению со временем поиска в разных пространствах памяти, если вы используете ссылочный способ.
Комментарии:
1. Спасибо вам за обстоятельный ответ. Спасибо вам и за последнее предупреждение.