Pull to refresh

Comments 21

То вы сетуете на производительность, то не хотите возиться с аллокациями… Вы уж определитесь. Увеличили с 50 сек до 20, проверьте что ли упрощенный вариант на С, вдруг там в 100 раз быстрее, а вы его ругаете?
Да, в этом и был смысл: можно ли писать «высокоуровнево», не возясь с низкоуровневыми вещами, при этом не сильно теряя в скорости?
Это интересный вопрос: а что, если писать на Си? К сожалению, я сам плохо пишу на Си, чтобы сравнивать. Могу привести несколько интересных ссылок со сравнениями (в том числе с Си):
1. www.ibm.com/developerworks/community/blogs/jfp/entry/A_Comparison_Of_C_Julia_Python_Numba_Cython_Scipy_and_BLAS_on_LU_Factorization?lang=en
2. modelingguru.nasa.gov/docs/DOC-2625
3. julialang.org/benchmarks (бенчмарки с официальной страницы Julia).
Проблемы всех бенчмарков в том, что их пишут либо предвзятые люди, либо специалисты в одном языке. По бенчмаркам видно, что 100х очень вряд ли…
Простите, что встреваю со своими тараканами, Julia действительно хорошо подходит для задач обработки данных и статистики, но не могу не порекомендовать язык D.
Вы получите: производительность C, можно не возиться с аллокациями (а можно и возиться, если это оправдано), хорошая библиотека алгоритмов (std.algorithm), mir — одна из самых быстрых либ для матриц, алгоритмов и линейной алгебры, во многом вдохновлена numpy, и много-много всего.

А в rust ничего подобного нет? Не совсем понимаю целевую аудиторию D

вместо решения задачи возишься с аллокациями и деструкторами

… что, на самом деле, означает вот что:

"я ниасилил аллокации и деструкторы"

Никто же не жалуется, что целые числа называются integer, а не whole. Это всё вопросы вкуса

Нет, это не вопросы вкуса. Это разница между традициями выбора научной лексики в английском и русском языком. Название для целых чисел в английском языке взято еще ~ в 17 веке из латинского языка, от in+tangere, «ненарушенный, нетронутый, чистый», а не образовано от собственно английского слова со значением «целый».
1. Думаю, вы верно всё прочитали между строк (это я о «не осилил»). Скажем так: я не думаю, что это очень сложная задача, тем более что есть много качественной литературы по языку Си и С++. Вопрос в том, действительно ли необходимо это знать тем, кто не занимается профессионально программированием, а использует его для каких-то своих целей?
Раньше вы могли сказать: я хочу, чтоб писать было легко, как на python, а летало бы не сильно медленнее Си, и вам бы справедливо ответили: «ты либо крест сними,… ». Я утверждаю, что теперь вы можете достичь скорости, сравнимой со скоростью Си, при этом не опускаясь на несколько уровней абстракции ниже, чем это требуется для решения задачи.

2. Спасибо за ликбез. Кстати говоря, «whole number» в английском языке есть, и так говорят. В учебниках правда не встречал.
между строк

Скажем так, если бы вы так и написали, что это вызвало затруднения лично у вас и в связи со спецификой вашей деятельности — саркастического комментария бы не было :-) В той же форме, как это написано в тексте — это воспринимается (несмотря на дисклаймер в следующем предложении) как «объективная» претензия в духе «зачем это нужно вообще?». Я бы на вашем месте написал что-то вроде «но вместо того, чтобы просто заняться решением моей задачи, я, увы, должен был бы разбираться со специфической для этого языка необходимостью корректного управления памятью».

и так говорят. (...) В учебниках

Ну есть же разница между обыденной речью и принятой технической терминологией, верно?
Небольшое замечание: я бы убрал лишнюю операцию их исходного алгоритма.
def longest(x):
    maxLen = 0
    currLen = 0

    # This will count the number of ones in the block
    for bit in x:
        if bit == 1:
            currLen += 1
        else:
            maxLen = max(maxLen, currLen)
            currLen = 0
    return max(maxLen, currLen)

50/20 секунд — это не особо впечатляющий прирост производительности (2.5 раза), не находите?


Вот тут есть некоторые занимательные бенчи
https://hackernoon.com/performance-analysis-julia-python-c-dd09f03282a3


И ещё есть такой комментарий на HN


This has been my experience as well: Python-style Julia code often does not run faster than in Python. Only of you rewrite it in C-style do you get C-like performance.
This is not necessarily a bad thing. Just an observation from me translating my Python code to Julia.

https://news.ycombinator.com/item?id=17204750

А не имеет ли С style тех же нeдостатков, что и cython?

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

Ну смотрите, по бенчмаркам из первого примера:
1. Версия 0.4.5, а сейчас уже 1.0.
2. (и это гораздо важнее) — замер времени с помощью tic-toc. Это неправильно из-за JIT-компиляции. Надо замерять время после «прогрева» и усреднять по большому количеству запусков; для этого в питоне есть %timeit (точнее в ipython); в Julia для этого есть макрос @benchmark.
3. В С-программе массив заранее аллокируется; в Julia используется динамический, без sizehint даже.
В целом, как мне кажется, я честно предупредил в статье, что нужен некоторый навык для написания программ на Julia (как и на любом другом языке).

2. По второму комментарию. Я привел пример в самой статье когда я буквально написал один и тот же код на Python и на Julia, второй работал гораздо быстрее. Это был python-style или c-style? Я на это могу возразить только своими личными ощущениями от написания кода на julia: на ней можно писать быстро, и код по моим ощущениям ближе (гораздо) к python.

Согласен, возможно в этой задаче не такой впечатляющий. По мне это довольно ощутимо. Я почти уверен, что на Си эта граница не сильно упадёт, просто из соображений сложности некоторых алгоритмов (дискретное преобразование Фурье, алгоритм Берлекэмпа-Месси и другие затратные тесты).

В плане работы JIT было бы интересно сравнить Julia и PyPy. :)

ИМХО, ускорение в 2.5 раза не стоит перехода на новый язык. Учитвая то, насколько Python силен экосистемой и батарейками.
Возможно и так, про экосистему согласен, но на Julia тоже появились довольно интересные проекты (на мой взгляд), и я не знаю, есть ли аналоги в Питоне, и если есть, то насколько они продвинутые?
К примеру:
1. flux (https://github.com/FluxML/Flux.jl) — машинное и глубокое обучение с возможностью писать очень разноуровневый код на pure Julia (от сложений на GPU до описания сетей в несколько строк).
2. DifferentialEquations (http://docs.juliadiffeq.org/latest/) — решатель всевозможных дифф уравнений, в т.ч. стохастических и прочей экзотики.
3. JuliaOpt (https://www.juliaopt.org/) — всяческие задачи оптимизации, в том числе DSL для постановки и решения оптимизационных задач — JuMP.
4. Грамматика графики Gadfly (http://gadflyjl.org/stable/).
5. Как любитель математики не могу не отметить связку Nemo-Singular-Hecke (http://nemocas.org/) — это конечно не SAGE, но тоже много интересного, от конечных полей до довольно нетривиальных вещей типа алгебраической теории чисел.
6. Некоторые «маленькие» вещи мне больше нравится в Julia, а именно — задание всяческих вероятностных распределений (пакет Distributions), аналог pandas (DataFrames).

Тем не менее, есть некоторые вещи, которые есть в Python и нет в Julia, и по которым я скучаю. К примеру, всякие простые модели машинного обучения мне больше нравятся в sklearn. Тут стоит отметить, что питоний код легко запускать из Julia (см., к примеру, на такое: github.com/cstjean/ScikitLearn.jl). Но по мне это как-то немного глупо — писать на одном языке, всё время используя штуки из другого )

Введение
Я был одним из тех, кто просил написать статью про переход с Python на Julia. Большое спасибо!


Прежде всего я хочу согласиться с автором статьи. Да, впихнуть невекторизуемые операции в NumPy порой очень непросто. Cython это страшно, а Numba это непредсказуемо. Именно поэтому я пару лет назад чуть не ушел с Python на что-то другое. Но в то время чего-то другого я не нашел, и остался на Python.


Сейчас я выступлю в роли защитника Python и все-таки продолжу утверждать, что будущее численных расчетов все-таки за Python


Улучшаем код на Python
Я взял код из статьи за основу и провел свои собственные тесты. Длина выборки N=10^6 элементов типа np.int32.


Тест 1. Чистый Python.


def get_longest_0(x):
    maxLen = 0
    currLen = 0

    for bit in x:
        if bit == 1:
            currLen += 1
        else:
            maxLen = max(maxLen, currLen)
            currLen = 0
    return max(maxLen, currLen)

Результаты: 3.167 s


ОК, это исходная точка.


Тест 2. Все-таки запихиваем все в NumPy
Автор статьи правильно начал рассуждать о том, что код можно векторизовать путем сдвига и вычитания. Но решил, что это не очень хорошее решение. Но я все-таки довел это способ до конца.


def get_longest_1(x):
    x_ext = np.hstack(([0], x, [0]))
    x_difs = np.diff(x_ext)
    lengths = np.where(x_difs < 0)[0] - np.where(x_difs > 0)[0]
    max_len = np.max(lengths)

    return max_len

Результаты: 0.189 s


Быстрее где-то в 16 раз.


Кроме скорости выполнения по мне так такой способ гораздо нагляднее, чем первый итерационный способ в цикле. Но для этого нужно сменить свой способ мышления. Нужно думать в векторизованном стиле. Даже скорее в стиле потокового вычислительного графа, где мы отделяем операции о данных. А для операций мы строим полусимвольный граф вычислений. Но об этом позднее.


Тест 3. И все-таки Numba
Код, как в Tecт 1, но всего одна аннотация.


@numba.jit
def get_longest_2(x):
    maxLen = 0
    currLen = 0

    for bit in x:\
    ...

и...


Результаты: 0.009 s


Еще раз в 20 быстрее, чем NumPy.


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


Рассуждение о будущем научных вычислений
Язык Julia можно рассматривать как продолжение линии Fortran, где нам нужно написать числодробилку для каких-нибудь сеточных методов в таком императивном стиле, плюс распараллелить все по типу OpenMP. В этом смысле язык Julia идеален.


Но проблема то в том, что для современного железа это слишком низкий уровень, не позволяющий достичь максимальной производительности. Современное железно это многоядерные (реально многоядерные) процессоры со своими уровня кэша + GPU. Потоки данных между таким железом нужно гонять достаточно нетривиальным образом для максимальной производительности.


В стиле Fortran эффективный код для этого написать крайне нетривиально. И старый добрый LAPACK уже не справляется.


Современный ответ на эту проблему это концепция потоковых полусимвольный графов вычислений. Что-то типа TensorFlow. Мы отделяем граф вычислений от потоков данных. Граф вычислений собираем из готовых блоков, которые автоматически распределяются под разнообразное железо.


На мой взгляд Julia слишком низкоуровневая для этого. А вот Python подходит под эту концепцию на все 100%.


А теперь поглядим на Cython и Numba с этой точки зрения. В Numba один и тот же код можно автоматически распараллелить для мультиядерных CPU аннотацией '@numba.jit(nopython=True, parallel=True)', а при желании автоматически все отправить на GPU аннотацией '@cuda.jit'.


Проект, где Cython зашел идеально, это cupy. Это интерфейс NumPy, но реализованный полностью на GPU. Более того, они предлагают вообще писать гетерогенный код, который с точки исходного кода полностью одинаков, а проблемы как эффективно выполнить его на GPU полностью берет на себя CuPy.


CuPy один из лучших примеров, как нужно писать на Cython. Cython тут выступает таким клеем между Python и низкоуровневым GPU кодом. Например, вот тут
https://github.com/cupy/cupy/blob/master/cupy/cuda/cublas.pyx один из немногих случаев, когда я читал Cython код без головной боли.


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

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

# without numba
%timeit get_longest_1(x)
14.2 ms ± 14.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

@numba.jit
def get_longest_2(x):
    x_ext = np.hstack(([0], x, [0]))
    x_difs = np.diff(x_ext)
    lengths = np.where(x_difs < 0)[0] - np.where(x_difs > 0)[0]
    max_len = np.max(lengths)
    return max_len


In [14]: %timeit get_longest_2(x)
12.5 ms ± 247 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)


По каким-то причинам моя numba старается не так сильно, как ваша! Примерно та же история была, когда я как раз в первый раз на python писал.
Замечу, что векторизация совершенно уравняла на данной конкретной задаче шансы.

По поводу GPU: советую всё же обратить немного внимания, например, на это: github.com/JuliaGPU
В частности: «Because CuArray is an AbstractArray, it doesn't have much of a learning curve; just use your favourite array ops as usual». AbstractArray — это общий тип всех массивов, что-то наподобие классов типов в Haskell.
По параллельным вычислениям — тоже кое-что реализовано (см. docs.julialang.org/en/v1/manual/parallel-computing). Т. е. это буквально тоже на уровне языка, делается в один макрос.
Также обратите внимание на проект Celeste (какие-то астрономические расчеты, я деталей не знаю к сожалению):
At the 2017 JuliaCon conference, Jeffrey Regier, Keno Fischer and others announced that the Celeste project used Julia to achieve «peak performance of 1.54 petaFLOPS using 1.3 million threads» on 9300 Knights Landing (KNL) nodes of the Cori (Cray XC40) supercomputer (the 5th fastest in the world at the time; 8th fastest as of November 2017). Julia thus joins C, C++, and Fortran as high-level languages in which petaFLOPS computations have been written.
Современный ответ на эту проблему это концепция потоковых полусимвольный графов вычислений.

На мой взгляд Julia слишком низкоуровневая для этого. А вот Python подходит под эту концепцию на все 100%.

Из чего вообще следует, что Julia низкоуровневая?
Честно говоря, нигде не встречал подобных аргументов как недостатков Джулии. Насколько я знаю, у неё нет проблем ни с символьными вычислениями, ни с вычислительным графом — см. IR representation. Да и разработана она была с заделом на параллельность и многопоточность, как напрямую следует из ее описания.

P.S. Для чистоты эксперимента надо бы добавить результаты на Julia на той же архитектуре…
Как оптимизировали, профилировщиком @profile пользовались?
Добавляли макросы типа, @inbounds, @simd, @fastmath?
Был какой-нибудь прирост за их счет?

Мы с коллегой как-то погоняли С++ и Julia на тупом алгоритме простых чисел — Julia оказалась быстрее на 15-20% (правда, для С++ был gcc, когда перешли на llvm, С++ стал быстрее на ~5%).
Sign up to leave a comment.

Articles