Когда вексеризация предпочтительна в Юлии?

У меня есть 2 функции для определения pi численно в Julia. Вторая функция (которая, я думаю, векторизована) медленнее первой. Почему векторизация медленнее? Существуют ли правила, когда для векторизации и когда не нужно?

function determine_pi(n)
    area = zeros(Float64, n);
    sum = 0;
    for i=1:n
        if ((rand()^2+rand()^2) <=1)
            sum = sum + 1;
        end
            area[i] = sum*1.0/i;
    end
    return area
end

и другая функция

function determine_pi_vec(n)
    res = cumsum(map(x -> x<=1?1:0, rand(n).^2+rand(n).^2))./[1:n]
    return res
end

При запуске для n = 10 ^ 7 ниже приведены времена выполнения (после нескольких запусков)

n=10^7
@time returnArray = determine_pi(n)
#output elapsed time: 0.183211324 seconds (80000128 bytes allocated)
@time returnArray2 = determine_pi_vec(n);
#elapsed time: 2.436501454 seconds (880001336 bytes allocated, 30.71% gc time)

Ответ 1

Векторизация хороша, если

  • Это упрощает чтение кода, а производительность не критична.
  • Если его операция с линейной алгеброй, используя векторный стиль, может быть хорошей, потому что Julia может использовать BLAS и LAPACK для выполнения вашей операции с очень специализированным высокопроизводительным кодом.

В целом, я лично считаю, что лучше всего начать с векторизованного кода, искать любые проблемы со скоростью, а затем детекционировать любые неприятные проблемы.

Ваш второй код медленный не столько из-за его векторизации, но из-за использования анонимной функции: к сожалению, в Julia 0.3, это, как правило, довольно немного медленнее. map в целом работает не очень хорошо, я считаю, потому что Julia не может вывести выходной тип функции (ее еще "анонимный" с точки зрения функции map). Я написал другую векторную версию, которая позволяет избежать анонимных функций и, возможно, немного легче читать:

function determine_pi_vec2(n)
    return cumsum((rand(n).^2 .+ rand(n).^2) .<= 1) ./ (1:n)
end

Бенчмаркинг с помощью

function bench(n, f)
  f(10)
  srand(1000)
  @time f(n)
  srand(1000)
  @time f(n)
  srand(1000)
  @time f(n)
end

bench(10^8, determine_pi)
gc()
bench(10^8, determine_pi_vec)
gc()
bench(10^8, determine_pi_vec2)

дает мне результаты

elapsed time: 5.996090409 seconds (800000064 bytes allocated)
elapsed time: 6.028323688 seconds (800000064 bytes allocated)
elapsed time: 6.172004807 seconds (800000064 bytes allocated)
elapsed time: 14.09414031 seconds (8800005224 bytes allocated, 7.69% gc time)
elapsed time: 14.323797823 seconds (8800001272 bytes allocated, 8.61% gc time)
elapsed time: 14.048216404 seconds (8800001272 bytes allocated, 8.46% gc time)
elapsed time: 8.906563284 seconds (5612510776 bytes allocated, 3.21% gc time)
elapsed time: 8.939001114 seconds (5612506184 bytes allocated, 4.25% gc time)
elapsed time: 9.028656043 seconds (5612506184 bytes allocated, 4.23% gc time)

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