Юля петли так же медленно, как петли R - PullRequest
0 голосов
/ 29 июня 2018

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

Джулии требуется ~ 10 секунд, чтобы закончить два цикла, а R делает это за ~ 7 секунд. Если я оставляю код внутри закомментированных циклов, то циклы в R и Julia занимают одно и то же время, и если я суммирую итераторы только по s = s + i+ j, то Julia заканчивает работу через ~ 0,15 с, а R - через ~ 0,5 с.

Неужели петли Джулии медленные или R стал быстрым? Как я могу улучшить скорость кода ниже для Джулии? Может ли код R стать быстрее?

Julia:

using Plots
trials = 100000
sample_size = 10;
sd = Array{Float64}(trials,sample_size-1)

tic()
for i = 2:sample_size
    for j = 1:trials
        res = randn(i)
        sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
    end
end
toc()
sd2 = mean(sd,1)
plot(sd2[1:end])

R

trials = 100000
sample_size = 10
sd = matrix(, nrow = trials, ncol = sample_size-1)
start_time = Sys.time()
for(i in 2:sample_size){
  for(j in 1:trials){
  res <- rnorm(n = i, mean = 0, sd = 1)
  sd[j,i-1] = (1/(i))*(sum(res*res))-(1/((i)*i))*(sum(res)*sum(res))

}
}

end_time = Sys.time()
end_time - start_time
sd2 = apply(sd,2,mean)
plot(sqrt(sd2))

Сюжет на случай, если кому-нибудь станет интересно !: enter image description here

Один из способов добиться гораздо более высокой скорости - использовать параллельный цикл, который очень легко реализовать в Julia:

using Plots
trials = 100000
sample_size = 10;


sd = SharedArray{Float64}(trials,sample_size-1)

tic()
@parallel for i = 2:sample_size
    for j = 1:trials
        res = randn(i)
        sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
    end
end
toc()
sd2 = mean(sd,1)
plot(sd2[1:end])

1 Ответ

0 голосов
/ 29 июня 2018

Использование глобальных переменных в Джулии в целом медленное и должно дать вам скорость, сравнимую с R. Вы должны обернуть свой код в функцию, чтобы сделать его быстрым.

Вот время от моего ноутбука (я вырезал только соответствующую часть):

julia> function test()
           trials = 100000
           sample_size = 10;
           sd = Array{Float64}(trials,sample_size-1)

           tic()
           for i = 2:sample_size
               for j = 1:trials
                   res = randn(i)
                   sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
               end
           end
           toc()
       end
test (generic function with 1 method)

julia> test()
elapsed time: 0.243233887 seconds
0.243233887

Кроме того, в Julia, если вы используете randn! вместо randn, вы можете ускорить его еще больше, поскольку вы избегаете перераспределения вектора res (я не делаю другие оптимизации для кода, так как эта оптимизация отличается от Julia по сравнению с R все другие возможные ускорения в этом коде помогли бы Юлии и R аналогичным образом):

julia> function test2()
           trials = 100000
           sample_size = 10;
           sd = Array{Float64}(trials,sample_size-1)

           tic()
           for i = 2:sample_size
               res = zeros(i)
               for j = 1:trials
                   randn!(res)
                   sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
               end
           end
           toc()
       end
test2 (generic function with 1 method)

julia> test2()
elapsed time: 0.154881137 seconds
0.154881137

Наконец, лучше использовать пакет BenchmarkTools для измерения времени выполнения в Юлии. Первые функции tic и toc будут удалены из Julia 0.7. Второе - вы смешиваете время компиляции и выполнения, если используете их (при двойном запуске функции test вы увидите, что при втором запуске время уменьшается, поскольку Джулия не тратит время на компиляцию функций).

EDIT:

Вы можете сохранить trials, sample_size и sd как глобальные переменные, но тогда вам следует добавить к ним префикс const. Тогда достаточно обернуть цикл в функцию, подобную этой:

const trials = 100000;
const sample_size = 10;
const sd = Array{Float64}(trials,sample_size-1);

function f()
    for i = 2:sample_size
        for j = 1:trials
            res = randn(i)
            sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
        end
    end
end

tic()
f()
toc()

Теперь для @parallel:

Во-первых, вы должны использовать @sync перед @parallel, чтобы убедиться, что все работает правильно (т. Е. Что все рабочие закончили, прежде чем перейти к следующей инструкции). Чтобы понять, почему это необходимо, запустите следующий код в системе с несколькими работниками:

sd = SharedArray{Float64}(10^6);
@parallel for i = 1:2
    if i < 2
        sd[i] = 1
    else
        for j in 2:10^6
            sd[j] = 1
        end
    end
end
minimum(sd) # most probably prints 0.0
sleep(1)
minimum(sd) # most probably prints 1.0

пока это

sd = SharedArray{Float64}(10^6);
@sync @parallel for i = 1:2
    if i < 2
        sd[i] = 1
    else
        for j in 2:10^6
            sd[j] = 1
        end
    end
end
minimum(sd) # always prints 1.0

Во-вторых, улучшение скорости связано с @parallel макросом, а не SharedArray. Если вы попробуете свой код на Юлии с одним работником, это также будет быстрее. Короче говоря, причина в том, что @parallel внутренне оборачивает ваш код внутри функции. Вы можете проверить это, используя @macroexpand:

julia> @macroexpand @sync @parallel for i = 2:sample_size
           for j = 1:trials
               res = randn(i)
               sd[j,i-1] = (1/(i))*(sum(res.^2))-(1/((i)*i))*(sum(res)*sum(res))
           end
       end
quote  # task.jl, line 301:
    (Base.sync_begin)() # task.jl, line 302:
    #19#v = (Base.Distributed.pfor)(begin  # distributed\macros.jl, line 172:
                function (#20#R, #21#lo::Base.Distributed.Int, #22#hi::Base.Distributed.Int) # distributed\macros.jl, line 173:
                    for i = #20#R[#21#lo:#22#hi] # distributed\macros.jl, line 174:
                        begin  # REPL[22], line 2:
                            for j = 1:trials # REPL[22], line 3:
                                res = randn(i) # REPL[22], line 4:
                                sd[j, i - 1] = (1 / i) * sum(res .^ 2) - (1 / (i * i)) * (sum(res) * sum(res))
                            end
                        end
                    end
                end
            end, 2:sample_size) # task.jl, line 303:
    (Base.sync_end)() # task.jl, line 304:
    #19#v
end
...