Pull to refresh

Comments 14

Python не перестаёт удивлять многих своей гибкостью и эффективностью

Кто-нибудь может это прокомментировать? Что это за гибкость и эффективность и как их измерить и сравнить для нескольких языков программирования?

Я думаю имеется в виду вот эта переопределяемость всего и вся которая используется в том же Pandas

Гибкость – да, язык модный, много пакетов на все случаи жизни. Эффективность – ну это смотря что считать эффективностью. Если скорость разработки, то да, эффективный. Если скорость вычислений, то нет (медленнее Экселевского VBA в 3-6 раз, ссылка).

В своем проекте я загружаю 31 млн записей за 4.6 секунды. Потом рассчитываю по всем этим записям индикаторы, например ema по ним считается за 0.095 сек (код на питоне, не готовая библиотека). Более сложные алгоритмы - максимум несколько секунд. Эксель так сможет?
https://github.com/hal9000cc/live_trading_indicators/blob/stable/benchmarks.ipynb

Умножение двух 1440 * 1440 матриц (https://github.com/lkno0705/MatrixMultiplication):
• Python: 651 с
• Excel: 210 с

Оба теста на моём компьютере (Pentium 4417U). На то, что написано в Readme.md (на ссылку не жмите - автоматически вставляется Хабром) не смотрите – автор сказал, что обновить результаты теста на Экселе не сможет – Экселя у него больше нет (копия переписки).

Чтение файла с 4 млн записей (опять же мой неторопливый 4417U): 120 мс из таблицы и 7 секунд из файла (включая парсинг на том же VBA).

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

Заглянул в код. В общем, там треш полный))

Это не питоновский код. Хотя я видел подобное, когда, к примеру, шарп-программист начинает писать на питоне.

Там даже совсем не те типы данных, которые не годятся для таких операций. Так что результат в 3 раза медленнее экселя - это достойно) Списки не надо тут использовать, они не для этого. Заменить их хотя бы на np.ndarray, и даже в таком виде питон, скорее всего, обогнал бы эксель.

Но я все же потрачу время и распишу подробнее, возможно, это поможет кому-то, развею какие-то мифы. Вообще, я сторонник фактов, поэтому пройдусь по ним.


1. Конкретно по задаче перемножения матриц. Матрицы на питоне перемножают так (сделал сейчас тест):

import timeit
import numpy as np

mat1 = np.random.random((1440, 1440))
mat2 = np.random.random((1440, 1440))
test_globals = {'mat1': mat1, 'mat2': mat2}

time = timeit.timeit('mat3 = mat1.dot(mat2)', number=1, globals=test_globals)
print(f'result: {time} seconds\n')
result: 0.03760418598540127 seconds

Вижу возражение - использовалась библиотека. Ок, принимается. Хотя для питона столько библиотек, что покрывают практически все задачи, где требуются ресурсы процессора. И не только процессора - мне хватит минут 5, что бы изменить этот тест так, что бы матрицы перемножились на GPU. Эксель там слышит это?))

  1. Как уже написал, так с вычислительными задачами на питоне не работают. Для них используются массивы numpy. Если уж хочется сравнить именно быстродействие языка, то стоит хотя бы переписать тест на массивы. Списки - это не массивы, они для другого.

  2. Да, в классической реализации питон не быстрый язык. Но если требуется скорость, то у него есть соответствующие инструменты. В частности, numba, о которой речь в статье. Там пишешь код на питоне, отлаживаешь, а потом перед ним просто ставишь модификатор, и он компилируется с помощью JIT в машинный код. Быстродействие этого кода становится максимальным - как у Rust/C/C++. Вот тестирование https://habr.com/ru/post/484142/
    Да, есть некоторые ограничения, не все вещи в языке поддерживается. Но там где нужно, это работает, и я это использую.

  3. Реализаций питона много. Например есть Jython, он компилирует в байт-код джавы. Его быстродействие, по идее, должно быть равно джаве. И то же самое есть для .net - IronPython. Но эти варианты я не пробовал.

Вообще, мне нравятся языки, которые могут компилироваться в машинный код - С/С++/Rust. На С++ опыт есть, интересно заняться Rust. Вот все жду момента, когда мне не хватит быстродействия питона. Уже свой проект и с десятками миллионов записей тестирую, и алгоритмы все сложнее... А вот - не приходит этот момент, хоть ты тресни!)) То нубма выручит, то что-то еще. Что-то уже начинаю бояться, что шансов изучить Rust у меня не будет...))

Добрый вечер, коллега!

Спасибо за развёрнутый ответ. К сожалению, я совсем не питонист и оценить качество кода не могу (читать могу, а вот писать, и уж тем более оптимизировать – нет). В том то всё и дело, что чистый Питон похоже медленнее чистого VBA (можете доказать обратное и сделать pull request в вышеобозначенный репозиторий). Честно говоря, сам был удивлён такому исходу.

А уж прикрутить к обоим языкам библиотеки это не проблема. Другое дело, что для VBA их придётся долго искать (может даже безуспешно).

Чтобы перемножить матрицы в VBA на GPU мне тоже понадобится минут 5 (дык! я ж автор ClooWrapperVba (статья на Хабре) – библиотеки для использования OpenCL (компиляция и исполнение OpenCL-кода на CPU и GPU) из VBA).

В одном, безусловно, соглашусь: для Питона столько сторонних библиотек, что Экселю и не снилось.

Rust конечно язык супер, но мои ручонки до него точно не доберутся. Пока хватает своего стека (C#, Swift, VBA, OpenCL и немного JS). Даже для поддержания себя в форме времени катастрофически не хватает.

В том то всё и дело, что чистый Питон похоже медленнее чистого VBA (можете доказать обратное и сделать pull request в вышеобозначенный репозиторий). 

Вот это вполне может быть, если VBA использует JIT, его питону не хватает. А, забыл же написать - есть еще pypy и там есть JIT! Только это все же не стандарт, и есть проблемы совместимости.
Жду, когда в стандартном питоне появится JIT. Уверен, что это не за горами. Ох, тогда точно не изучу Rust )))

Перемножил все же на GPU для интереса. RTX3090. Времени пришлось отобразится с экспонентой, 87 микросекунд)))

import timeit
import torch

mat1 = torch.rand((1440, 1440)).cuda()
mat2 = torch.rand((1440, 1440)).cuda()
test_globals = {'mat1': mat1, 'mat2': mat2}

time = timeit.timeit('mat3 = mat1.matmul(mat2)', number=1, globals=test_globals)
print(f'result: {time} seconds\n')
result: 8.713093120604753e-05 seconds

А что вы имеете ввиду?
Если тест, где матрицы перемножаются в numpy - согласен.

А вот что не нужно использовать массивы numpy в тесте - тут не соглашусь.

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

Любой тест имеет прикладную направленность. Если хотим узнать скорость языка в арифметике - надо тестировать именно скорость языка в арифметике. А там тест сравнил скорость арифметики VBA со скоростью создания объектов в питоне. Ну да, считать быстрее чем создавать объекты))


Мое мнение - если сравнивать арифметику, надо в тесте на питоне использовать массивы numpy, но перемножить матрицы кодом на питоне. Это будет честное сравнение. И это именно тот кейс использования, который будет в реальности при написании какой-либо арифметики на питоне, если нет готовой библиотеки.

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

К сожалению, не приведу цифры, так как уже нет времени, опишу словами.

  1. В том тесте (https://github.com/lkno0705/MatrixMultiplication) создание матриц в списках вынесено за измерение. А уже созданные списки достаточно быстры, они не являются тормозами. Поэтому тот тест можно использовать для оценки быстродействия арифметики чистого питона. Я попробовал несколько других вариантов, мне удалось чуть-чуть ускорить этот тест, но совсем немного, в пределах 10%. VBA действительно быстрее, наверняка использует JIT.

  2. Оказалось, что массивы numpy, которые я жутко хвалил, при таком кейсе медленнее списков! Причем сильно медленнее, раза в два! Тут, видимо, сказывается, что они не родные для питона, и тормозят прослойки. Но память они расходуют эффективнее, данные там лежат в памяти одним куском.

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

  4. Когда вчера писал комментарии, я забыл, что в питоне тоже есть свои, родные массивы. Я крайне редко вижу их использование, и сам не использовал. Причина такого понятна - для обычного кода используют универсальные списки, а для численных расчетов numpy. А массивы словно незаслуженно забыты, а зря. Данные в них так же лежат в памяти одним куском, у них почти весь функционал списков, только данные должны быть одного типа и поддерживаемые числовые - float, double, int, byte и другие целые разного размера, знаковые и беззнаковые. В общем, если тип поддерживается, то нет никаких причин не использовать массив вместо списка - это будет чуточку быстрее и компактно в памяти. Если нужен расчет обычным кодом на питоне, то нужно использовать именно их. Именно с их помощью я немного ускорил тот тест. Сами массивы одномерные, и я использовал список массивов.

  5. По итогу, если нужно максимальное быстродействие в питоне, то:

    • Используем numpy, если там есть что нужно, получаем быстродействие хорошо оптимизированного C.

    • Используем массивы numpy, пишем код на питоне и ускоряем нумбой с nopython=True, получаем быстродействие C. Нумба с массивами numpy работает очень хорошо.

    • Если не можем ускорить, то используем нативные питоновские массивы array, тут уже будет быстродействие питона.

В этом и прелесть Питона, в возможности писать хороший код, который потом либы обернут в код оптимальный на оптимальном языке

Это модная самоподдерживающаяся религия, чудовищным образом пробравшаяся и в научные круги. Огромное количество человек абсолютно безосновательно в это верит.

Sign up to leave a comment.