Как стать автором
Обновить

Комментарии 47

И ни слова про PyPy, Cython, Numba, NumPy)

а зачем?

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

А где можно почитать про обертку С. И насколько хорошо надо знать С чтобы эти обертки накладывать?

Здесь почитать. Здесь за вас уже что-то обернули. Знания нужны на уровне инклудов и указателей (хотя бы понимать что это и как работает). На гитхабе можно посмотреть что уже из стандартных библиотек обернуто и использовать.

А так же Pythran и Nuitka

Всё-таки питон нужен для быстрой и удобной разработки легко поддерживаемого кода.
Если вы настолько упёрлись в ограничения производительности, что вам приходится переписывать импорты и их использование в коде, лишь бы там не было обращения к атрибуту через точку — то вы изначально выбрали не тот язык для вашего проекта.

или выбрали слишком слабое железо

Ну что вы, это же не java
Всё-таки питон нужен для быстрой и удобной разработки легко поддерживаемого кода.

А остальные высокоуровненые языки программирования разве нужны для чего-то другого?

НЛО прилетело и опубликовало эту надпись здесь

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

Вот мне интересно, а чем питон быстрее той же скалы для маленьких программ?


Единственное, что приходит в голову: питон предустановлен во многих линукс-дистрибутивах, а jvm и sbt — нет. Но с другой стороны, если я владею скалой, скорее всего, окружение у меня уже давно готово и настроено, и нет никаких причин не писать даже маленькую программу на скале.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
А почему не используете статическую типизацию в питоне?

Использую её вовсю, но есть проблемы:


  1. В рантайме типы могут быть другими. Программа будет работать, но потом окажется, что вместо float у меня numpy.Scalar или что похуже.
  2. У питоновских библиотек типа numpy и tensorflow аннтоаций типов нет — приходится либо писать типизированные обёртки, либо смириться.
  3. Я регулярно сталкиваюсь с ситуациями, когда системой типов питона происходящее плохо описывается. Например, в np.zeros(shape=(3,4), dtype=np.float) возвращаемый тип зависит от аргументов. А функция загрузки png картинок может возвращать массивы с shape=(h,w), (h, w, 1), (h, w, 3) и (h, w, 4) и типы np.uint8 и np.float в зависимости от того, чем и с какими опциями картинки были сохранены. Дизайн некоторых библиотек сделан под динамическую типизацию и натянуть происходящее на статику может быть сложно.
  4. Питон изначально делался под динамическую типизацию и в нём нет многих фишек статических языков: например, мне не хватает extension методов, перегрузки функций и нормальных интерфейсов. Перегрузку функций можно сделать через декораторы, но технически внутри будет много-много проверок типов, а не вызов сразу нужной функции. С интерфейсами тоже есть какие-то подвижки типа модуля abc, но опять же в других языках с этим лучше.
Например, в np.zeros(shape=(3,4), dtype=np.float) возвращаемый тип зависит от аргументов. А функция загрузки png картинок может возвращать массивы с shape=(h,w), (h, w, 1), (h, w, 3) и (h, w, 4) и типы np.uint8 и np.float в зависимости от того, чем и с какими опциями картинки были сохранены
— дженерики?
НЛО прилетело и опубликовало эту надпись здесь

Это аннотация типов, зачем вы подменяете понятия? В CPython нет никакой статической типизации!

А чем питону мешает динамическая типизация? Никогда не возникало проблем с этим — всегда знаешь что должно быть в переменной по логике программы, если писать комментарии, логично рассуждать и документировать апи. Уже не первый раз слышу про динамическую типизацию в питоне в подобном ключе — что это его ложка дёгтя. Решительно не понимаю подобных проблем… В конце концов, если напутали с типами данных — то проект по хорошему должен быть покрыт тестами и тесты сразу же скажут где и что поправить, это же не беда вселенского масштаба?

Плюсую. Никогда не понимал это возни и кудахтанья насчет типов. Сколько работаю с Python, я всегда знаю, какой тип у МОЕЙ переменной. И если я сам этот тип не изменю, его никто не изменит!

Кааак сказать. Динамика хороша, иногда очень хороша, а иногда просто потрясающа. Утиная типизация иногда творит чудеса — просто за счёт реализации необходимых методов любая библиотека может начать работать с моим типом — разве не чудо? Не надо декларировать наследование определённого интерфейса, которые будут разными у разных библиотек — можно просто сделать что-то — и всё будет работать. Like a charm!


Но. Оборотная сторона медали в потрясающем хаосе типизации. Ты никогда, ни в чём, абсолютно не можешь быть уверен. Вернётся тебе строка, bytearray или пользовательский string-like? Причём, что забавно, если в python2 это действительно практически взаимозаменяемые понятия, как только мы вспоминаем про мультибайтовые строки — всё переворачивается. python3 уже, внезапно, не так классно работает с этим всем многообразием, потому что строки — это UTF-строки, а bytearray — просто массив байт. Это хорошо в том смысле, что можно из коробки легко-непринуждённо нормально работать со строками. Но со старыми библиотеками будут проблемы. Равно как и несколько сломанный API. Подход, что строки — это буффер также сломан. Что в целом правильно. Но.


Вспоминаем про утиную типизацию. И строки — и буфера реализуют абсолютно идентичный набор методов. Просто строки магически раскрываются на UTF-8 последовательности, а буффера это набор байт и всё. Хотя интерфейсы абсолютно идентичные.


Соль в том, что для утиной типизации идентичный интерфейс равнозначно идентичной семантике. Но это не правда. Если что-то выглядит как утка, плавает как утка и крякает как утка — совсем не обязательно, что 1. это утка, а не селезень, например, 2. она вообще существует, а не плод вашего воображения, 3. никто особо хитрый не маскирует аллигатора под утку.


Как обычно. У всего свои плюсы и минусы. Серебрянной пулей в каком-то смысле считают сильный вывод типов, но он в общем-то не решает проблемы явного указания типа, а без этого по факту не решить задачу разрешения семантики.


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


Ну и да. Идеальный проект должен быть покрыт тестами. Идеальный программист не ошибается. Идеальный тестировщик находит все ошибки. Идеальный заказчик формирует дотошные требования. Идеальный пользователь идеально предсказуем. Но есть наш идеально не идеальный мир. Жить надо с ним, а не в мечтах. Ну это так. Лирика.

А чем питону мешает динамическая типизация?

Для программиста динамическая типизация удобна. Она не удобна компьютеру :)
Например, некоторая процедура принимает параметр А типа int, и в ней написано А+1. В случае статической типизации это будет одна (!) команда процессора. В случае динамической типизации начнутся пляски воокруг А на тему «а что там, вообще, лежит?» и «что означает этот + ?». Этот простейший +1 будет выполнятся в разы медленнее. Собственно, это базовая причина, по которой языки с динамической типизацией никогда не смогут достичь быстродействия языков со статической типизацией.

Динамическая типизация не настолько уж и неудобна компьютеру, достаточно умные компиляторы™ её умеют оптимизировать (точнее, вычленять куски, в которых типы статически выводятся и не меняются, и JITить их).


Но вот пайтоновская полностью утиная типизация — она даже программисту далеко не всегда удобна, аннотации типов не просто так вводят.

НЛО прилетело и опубликовало эту надпись здесь

Она просто мешает именно вам. И кому-то еще, кто на Python тоже не пишет. Я одинаково люблю С и Python, не испытывая необходимости агитировать против утиной типизации.

НЛО прилетело и опубликовало эту надпись здесь
в этом то и проблема, что выйдет так что большинство тестов и будут напрямую или косвенно проверять тип выходной переменной.

Опыт — это конечно хорошо, но у каждого он свой. Можно ли узнать, какие конкретно фичи питона позволяют писать быстрее и более поддерживаемый код, чем в случае C#?

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
def fast_function():
r = random.random
for i in range(10000):
print(r()) # здесь вызов `r()` быстрее, чем был бы вызов random.random()
Это и так понятно, что быстрее будет работать, поскольку в цикле мы обращаемся постоянно к объекту, это в большинстве языков программирования так не верно делать, априори, иногда такая точка в цикле может влиять на скорость выполнения решения на 90% (лично сталкивался с таким)

Это в каких таких "большинстве"? В Java виртуальный метод в JIT разрезолвится, в C++/C# есть невиртуальные методы, там вообще резолвить нечего. Rust? Haskell? Objective C? Javascript?.. Даже он в JIT компилируется с адресной арифметикой с классическим прототипным наследованием.


Python один из редких языков с лексическим пространством имён. Вообще, именно поэтому сами по себе скрипты катастрофически медленные. Но особой магии python не требует — знай себе, что кешируй всё подряд и не стоит дробить на нём числа — для этого есть numpy.

main надо вызывать вот так:
if __name__ == '__main__':
    main()
А что с результатами-то? Вот в начале приводится медленная функция. Потом советы, как ускорить выполнение кода. А к медленной функции их применить и результат показать?
Или советы не применимы к ней, как и к 90% остального python-кода?
автор:
>Ненавистники Python всегда говорят, что Python — это медленно. Но то, что некая программа, независимо от используемого языка программирования, может считаться быстрой или медленной, очень сильно зависит от разработчика, который её написал, от его знаний и от умения создавать оптимизированный и высокопроизводительный код.

он же:
>Дело тут, в основном, в том, что встроенные механизмы языка реализованы средствами C. Если описывать нечто средствами Python — нельзя добиться того же уровня производительности.
Не вижу тут противоречий, если вы на это намекаете. Медленно или быстро — субъективная оценка, каждый сам для себя решает что для него быстро или медленно. То что условно одинаковая конструкция на питоне медленнее такой же на C, не означает что программа работает медленно.
1. Преждевременные оптимизации — зло.
Узнай место, которое замедляет программу, реализуй его на C(*).
Собственно про профилирование кода сказали, а дальше свернули куда-то не туда и стали говорить про всякие микрооптимизации, зачем?
*) по аналогии — мало кто пишет весь код сейчас на assembler, если узкое место CPU находят самую прожорливую ф-ию и переписывают её и только её (например используя параллельность по данным через SSE инструкции)

2. Не упоминуть про CPython (и другие интерпретаторы байткода), почему?

3. На фоне 1,2 то что описано в статье даст весьма незначительные улучшения.

3.1 Вот это чрезмерная оптимизация, даже в сравнении с остальными пунктами статьи.
>> Как это возможно? Дело в том, что при обработке большого набора данных без использования генераторов (итераторов) данные могут привести к переполнению L1-кэша процессора, что значительно замедлит операции по поиску значений в памяти.

Даже на фоне присутствующих в статье советов это уж совсем экономия на спичках.
Интерпретатор всё равно загрузится в L1-instraction-cache (который свой для кода\данных).
А уж пара промахов по data-cache дадут доли процентов на фоне общей неспешности внутренних мехазинмов python, типа __getattribute__ (которые вовсе не для производительности так проектировались).
Для исследования временных показателей не указан прекрасный стандартный модуль — timeit

timeit — игрушечка. Наткнулся сегодня на новую библиотеку для профайлинга, djn ссылка, библиотека scalene. Запустил тест функции, которую приводит автор в самом начале, вот результат:
python -m scalene main.py


main.py: % of CPU time = 100.00% out of  14.46s.
         | CPU %    | CPU %    |
  Line   | (Python) | (C)      | [main.py]
--------------------------------------------------------------------------------
     1   |          |          | import sys
     2   |          |          |
     3   |          |          | import timeit
     4   |          |          |
     5   |          |          | import time
     6   |          |          | from   functools import wraps
     7   |          |          |
     8   |          |          | from   decimal import *
     9   |          |          |
    21   |          |          | def timeit_wrapper(func):
    22   |          |          |     @wraps(func)
    23   |          |          |     def wrapper(*args, **kwargs):
    24   |          |          |         start = time.process_time()
    25   |          |          |         func_return_val = func(*args, **kwargs)
    26   |          |          |         end = time.process_time()
    27   |          |          |         print('{0:<10} {1:<8} {2:^8}'.format(
    28   |          |          |             'module', 'function', 'time'))
    29   |          |          |         print('{0:<10}.{1:<8} : {2:<8}'.format(
    30   |          |          |             func.__module__,
    31   |          |          |             func.__name__,
    32   |          |          |             end - start))
    33   |          |          |         return func_return_val
    34   |  67.97%  |  32.03%  |     return wrapper
    35   |          |          |
    36   |          |          | @timeit_wrapper
    37   |          |          | def exp(x):
    38   |          |          |     getcontext().prec += 2
    39   |          |          |     i, lasts, s, fact, num = 0, 0, 1, 1, 1
    40   |          |          |     while s != lasts:
    41   |          |          |         lasts = s
    42   |          |          |         i += 1
    43   |          |          |         fact *= i
    44   |          |          |         num *= x
    45   |          |          |         s += num / fact
    46   |          |          |     getcontext().prec -= 2
    47   |          |          |     return +s
    48   |          |          |
    49   |          |          | def test():
    50   |          |          |     exp(Decimal(3000))
    51   |          |          |
    52   |          |          | if __name__ == '__main__':
    53   |          |          |
    54   |          |          |     if sys.argv[-1] == 'timeit':
    55   |          |          |         print('timeit',
    56   |          |          |               timeit.timeit("test()",
    57   |          |          |               setup = "from __main__ import test",
    58   |          |          |               number = 1))
    59   |          |          |     else:
    60   |          |          |         test()
    61   |          |          |
    62   |          |          | """
    63   |          |          | Способы запуска:
    64   |          |          | 1. time python3 main.py
    65   |          |          | 2. python3 -m cProfile -s time main.py
    66   |          |          | 3. python3 main.py timeit # но внутри
    67   |          |          | 4. python3 -m scalene main.py
    68   |          |          | ""

Интересненько. А как с пакетами это работает? Он содержимое всех пакетов выведет или как? Интересно django-приложение как отпрофилирует.

Статья интересная, но не хватает данных.
В первой части статьи описаны механизмы замеров времени, а дальше просто рекомендации. Насколько быстрее стало после изменения кода?
На сколько процентов улучшается ситуация с применением Ваших рекомендаций?
Один товарищ переписал программу с С++ на Ассемблер для решения проблем производительности при решении сложных расчетных задач. В результате решение задач ускорилось в 7-9 раз. И оптимизация происходила на уровне использования регистров процессора.
В результате решение задач ускорилось в 7-9 раз. И оптимизация происходила на уровне использования регистров процессора.

Уоу. Крутяк =)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий