Является ли язык Джулии столь же быстрым, как он утверждает?

Следуя этот пост, я решил сравнить Юлию с GNU Octave и результаты были несовместимы с ускорениями, проиллюстрированными в julialang.org.

Я собрал как Julia, так и GNU Octave с CXXFLAGS='-std=c++11 -O3', результаты, которые я получил:

GNU Octave

a=0.9999;

tic;y=a.^(1:10000);toc
Elapsed time is 0.000159025 seconds.

tic;y=a.^(1:10000);toc
Elapsed time is 0.000162125 seconds.

tic;y=a.^(1:10000);toc
Elapsed time is 0.000159979 seconds.

-

tic;y=cumprod(ones(1,10000)*a);toc
Elapsed time is 0.000280142 seconds.

tic;y=cumprod(ones(1,10000)*a);toc
Elapsed time is 0.000280142 seconds.

tic;y=cumprod(ones(1,10000)*a);toc
Elapsed time is 0.000277996 seconds.

Джулия

tic();y=a.^(1:10000);toc()
elapsed time: 0.003486508 seconds

tic();y=a.^(1:10000);toc()
elapsed time: 0.003909662 seconds

tic();y=a.^(1:10000);toc()
elapsed time: 0.003465313 seconds

-

tic();y=cumprod(ones(1,10000)*a);toc()
elapsed time: 0.001692931 seconds

tic();y=cumprod(ones(1,10000)*a);toc()
elapsed time: 0.001690245 seconds

tic();y=cumprod(ones(1,10000)*a);toc()
elapsed time: 0.001689241 seconds

Может кто-нибудь объяснить, почему Джулия медленнее, чем GNU Octave с этими основными операциями? После нагревания он должен называть LAPACK/BLAS без накладных расходов, правильно?

EDIT:

Как поясняется в комментариях и ответах, приведенный выше код не является хорошим ориентиром и не иллюстрирует преимущества использования языка в реальном приложении. Раньше я думал о Джулии как о более быстрой "Октаве/МАТЛАБ", но это намного больше. Это огромный шаг на пути к продуктивным, высокопроизводительным научным вычислениям. Используя Julia, я смог: 1) превзойти программное обеспечение в моей области исследований, написанное на Fortran и С++, и 2) предоставить пользователям гораздо более удобный API.

Ответ 1

Векторизованные операции, такие как .^, являются именно тем, что хорошо подходит Octave, потому что они фактически полностью реализованы в специализированном C-коде. Где-то в коде, который скомпилирован при построении Octave, есть функция C, которая вычисляет .^ для двойника и массив двойников - то, что вы на самом деле выбираете здесь, и это быстро, потому что оно написано на C. Julia Оператор .^, с другой стороны, написан в Джулии:

julia> a = 0.9999;

julia> @which a.^(1:10000)
.^(x::Number,r::Ranges{T}) at range.jl:327

Это определение состоит из этого:

.^(x::Number, r::Ranges) = [ x^y for y=r ]

Он использует одномерное понимание массива, чтобы поднять x до каждого значения y в диапазоне r, возвращая результат как вектор.

Эдвард Гарсон вполне прав, что нельзя использовать глобальные переменные для оптимальной работы в Джулии. Причина в том, что компилятор не может очень хорошо рассуждать о типах глобальных переменных, потому что они могут меняться в любой момент, когда выполнение выходит из текущей области. Выход из текущей области не звучит так, как часто бывает, но в Julia даже основные вещи, такие как индексирование в массив или добавление двух целых чисел, фактически являются вызовами метода и, таким образом, оставляют текущую область. Однако в коде в этом вопросе все время проводится внутри функции .^, поэтому факт, что a является глобальным, на самом деле не имеет значения:

julia> @elapsed a.^(1:10000)
0.000809698

julia> let a = 0.9999;
         @elapsed a.^(1:10000)
       end
0.000804208

В конечном счете, если все, что вы когда-либо делаете, это вызывать векторизованные операции с массивами с плавающей запятой, Octave просто отлично. Тем не менее, это часто не на самом деле, где большая часть времени проводится даже на динамических языках высокого уровня. Если вы когда-нибудь захотите итерации по массиву с циклом for, работая над каждым элементом со скалярной арифметикой, вы обнаружите, что Octave довольно медленный в таких вещах - часто в тысячи раз медленнее, чем код C или Julia тоже самое. С другой стороны, писать для петель в Джулии - это вполне разумная вещь - на самом деле весь наш код сортировки написан в Джулии и сопоставим с показателем C. Есть также много других причин использовать Юлию, которая не связана с производительностью. Будучи клоном Matlab, Octave наследует многие проблемы дизайна Matlab и не очень хорошо относится к языку программирования общего назначения. Например, вы не хотели бы писать веб-сервис в Octave или Matlab, но довольно легко сделать это в Julia.

Ответ 2

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

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

Простые модификации вашего кода в соответствии с https://docs.julialang.org/en/stable/manual/performance-tips/ должны давать более удовлетворительные результаты.