LINUX.ORG.RU
ФорумTalks

Быстрый и логичный пузон

 ,


1

2

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

  • np.zeros(5)[list(range(1,3))] - 2.4 us
  • np.zeros(5)[range(1,3)] - 5.1 us

а ведь вроде list() это плюс еще один вызов. ну это еще ладно, генераторы лучше убрать в чулан, но вот дальше

def test1():
    a = np.zeros(5)
    c = list(range(1,3))
    for _ in range(0, 1000):
        a[c] += 5
def test2():
    a = np.zeros(5)
    c = list(range(1,3))
    for _ in range(0, 1000):
        a[c] = np.add(a[c], 5)
  • test1() - 4ms
  • test2() - 3ms (с a[c] = a[c] + 5 аналогично)

И судя по всем тестам оператор += в 90% случаев (кроме сложения целых чисел) тормознее чем тупое присвоение.

Веселее только то что если предварительно сделать что-то типа npadd = np.add, то работать оно будет еще чуть быстрее, потому что np.add каждый раз берет сначала указатель на np, а затем на add()

Или вот сейчас совсем вскрыло, хоть это вообще второй пузон и с быстродействием не связано:

>>> unicode(None)
u'None'
★★★★★

Последнее исправление: upcFrost (всего исправлений: 3)

Ты уверен, что тебе нужен пузон?

Deleted
()
Ответ на: комментарий от Deleted

Вот где-то на этом моменте обычно советуют взять другой язык

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от proud_anon

Ну тут надо разбираться, в чём дело: в Питоне или в NumPy

во взаимодействии пузона с сишными либами и не только. Хотя скажем судя по всему реализация += просто кривая

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

вроде давно зареган, а изъясняешься как школьник.

аналитикам

а им так нужны мелкие оптимизации в пару мс ? даже если оно выльется в пару минут ничего страшного не произойдет. В пандасе куда страшнее, что он очень быстро ОЗУ может сожрать если расслабиться.

Dred ★★★★★
()
Ответ на: комментарий от Dred

а им так нужны мелкие оптимизации в пару мс

им нет. а вот заказчику да. И эта «пара мс» легко выливается в пару десятков минут в зависимости от объема данных.

В пандасе куда страшнее, что он очень быстро ОЗУ может сожрать если расслабиться.

было дело, особенно когда идут join

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от dmxrand

А ну да у нас суп из C либы и Python, а виноват Python

ты знаешь что такое numpy, зачем он нужен и как он работает? если нет - проходи, смотреть тут нечего

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Чем запускаешь? PyPy пробовал?

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

upcFrost ★★★★★
() автор топика

Оператор += пораждает новый объект.

 $ python
Python 2.7.9 (default, Jun 29 2016, 13:08:31) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> s = "Hello "
>>> id(s)
140419164255408
>>> s += "world!111"
>>> id(s)
140419164262560
>>> 

Yur4eg ★★
()
Ответ на: комментарий от Yur4eg

Оператор += пораждает новый объект

Не обязательно

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> s = "Hello "
>>> id(s)
140121813425248
>>> s += "world!111"
>>> id(s)
140121813427512

Но

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> s = "Hello "
>>> id(s)
140160211250272
>>> s += "world"
>>> id(s)
140160211250272

Удивительно? Нет

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Удивительно? Нет

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от irr123

ты заметил что во втором случае пистон вернул тот же объект? вот именно такое «почти» весьма сильно вымораживает. типа «да, все збс, но только если не...» и тут еще здоровый список самых невероятных вариантов развития сюжета

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от proud_anon

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

t184256 ★★★★★
()

2.4 us

Я надеюсь ты хотя бы по нескольким секундам повторений это мерил, в rtprio и без других запущенных задач на машине?

slovazap ★★★★★
()
Ответ на: комментарий от t184256

с питоном времени моей юности

Python 3.5.2 (default, Nov 17 2016, 17:05:23)

вставная челюсть не жмет?

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Я надеюсь ты хотя бы по нескольким секундам повторений это мерил

среднее из 10млн запусков, через timeit

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от irr123

поведение строк вообще мощно поменяли со второй на третью ветку. я имею ввиду что есть сильно ненулевой риск напороться на неочевидное/непредсказуемое поведение

upcFrost ★★★★★
() автор топика

Справедливости ради, попробуй еще c = np.arange(1, 3)

А так да, толсто

michwill ★★★★★
()
Ответ на: комментарий от dmxrand

Python и предназначен для того, чтобы был суп из библиотек и python. Единственный серьёзный недостаток у python сейчас в том, что цена вызова функции высока.

Quasar ★★★★★
()
Ответ на: комментарий от tailgunner

Тебе нужно читать, как ускорять NumPy, а не обычный Python

проблема не в ускорении NumPy, а в передаче данных между питоном и либами. собственно из первого примера видно что цена этой передачи весьма некислая (генератор же по сути будет гонять туда-сюда за каждым значением). Делал еще эксперимент типа np structured array против цикла for, получилось что на аналогичном примере смысл юзать structarray есть только когда у цикла более 500 итераций. А про какой-нибудь пандас для этого дела вообще молчу - эта шарманка зарылась в сеттеры и индексацию и так там и сдохла

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от Quasar

типизация-то пройдет, там int/float везде. PyPy страшно юзать из-за неполной совместимости. Мало ли где она вылезет

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

проблема не в ускорении NumPy, а в передаче данных между питоном и либами.

И выплывает костыль: передать за раз как можно больше данных чтобы понизить оверхед.

Quasar ★★★★★
()
Ответ на: комментарий от upcFrost

Если у тебя есть реальная задача, которую тебе не хватает скилла ускорить одним numpy, реши ее уже на Cython.

Если нет — хорош разглагольствовать с дивана.

t184256 ★★★★★
()
Ответ на: комментарий от Quasar

Статически типизирован только RPython, на котором написан сам PyPy

Deleted
()
Ответ на: комментарий от upcFrost

а в передаче данных между питоном и либами

Тогда только брать C/Cython и устранять проблему в корне в критических местах.

Deleted
()
Ответ на: комментарий от upcFrost

проблема не в ускорении NumPy, а в передаче данных между питоном и либами

Это именно проблема ускорения NumPy. Или, если хочешь, ускорения программ, написанных на Python с использованием NumPy.

собственно из первого примера видно что цена этой передачи весьма некислая

Я не вижу некислой цены, но из прошлой жизни и опыта работы с NumPy помню, что да, рекомендуется избегать частого перехода между Python и Cи-ядром NumPy - использовать ufuncs и ТНБ знает что еще. У NumPy есть руководство по оптимизации, и, насколько я могу судить, обе твои функции ему противоречат.

tailgunner ★★★★★
()
Ответ на: комментарий от t184256

Если у тебя есть реальная задача, которую тебе не хватает скилла ускорить одним numpy, реши ее уже на Cython

думали об этом. Cython не очень ввиду компиляции, что сразу сажает на очень нехорошую поддержку (вот заказчик взял и обновил libc) плюс несколько затрудняет сборку/отладку

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

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

собственно это и есть «некислая цена».

У NumPy есть руководство по оптимизации, и, насколько я могу судить, обе твои функции ему противоречат

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

по сути просто не ожидал что разница будет настолько жесткой, периодически на порядок быстрее/дольше

да и вообще я к питону имею весьма отдаленное отношение, что-то типа 80% жаба, 20% питон

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

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

собственно это и есть «некислая цена».

Это типичный случай «доктор, когда я делаю вот так - больно».

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

Тем более - зачем сравнивать 2 куска, написанных против правил? Перепиши один рекомендованным способом и сравни.

периодически на порядок быстрее/дольше

Измерение производительности маленьких, ничего не делающих кусков кода - дело тонкое.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Измерение производительности маленьких, ничего не делающих кусков кода

обоснуй почему они ничего не делают? это просто модель, примерно повторяющая кусок реального кода. просто например в случае с += там пропущена пара вычислений, да и основной контейнер это переменная, а не толстый dict

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Cython не очень ввиду компиляции, что сразу сажает на очень нехорошую поддержку (вот заказчик взял и обновил libc)

и?

плюс несколько затрудняет сборку/отладку

по-моему ты просто говоришь что в голову пришло. может тебе начать с numba или numexpr?

t184256 ★★★★★
()
Ответ на: комментарий от upcFrost

Измерение производительности маленьких, ничего не делающих кусков кода

обоснуй почему они ничего не делают?

Насколько я понимаю, они увеличивают 3 элемента массива - это практически ничего. Ты замеряешь накладные расходы. Ну да, в одном случае они в 2 раза больше - и что?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

они увеличивают 3 элемента массива - это практически ничего

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

я просто искал кто тормозит, и последовательно выкидывал куски если они не слишком влияли на производительность

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

они увеличивают 3 элемента массива - это практически ничего

в реальной жизни это могут быть 3 разных элемента каждый раз

Они и сейчас разные.

просто искал кто тормозит, и последовательно выкидывал куски если они не слишком влияли на производительность

И ты пришел к длинным Python-циклам, в которые завернуты маленькие numpy-операции? Трудно в это поверить,

tailgunner ★★★★★
()

А ты точно сварщик настоящий?

И судя по всем тестам оператор += в 90% случаев (кроме сложения целых чисел) тормознее чем тупое присвоение.

>>> from dis import dis
>>> def x():
...     foo = foo + 1
... 
>>> dis(x)
  2           0 LOAD_FAST                0 (foo)
              3 LOAD_CONST               1 (1)
              6 BINARY_ADD          
              7 STORE_FAST               0 (foo)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
>>> def y():
...     foo += 1
... 
>>> dis(y)
  2           0 LOAD_FAST                0 (foo)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD         
              7 STORE_FAST               0 (foo)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
>>>

Разница в инструкциях BINARY_ADD и INPLACE_ADD.
Смотрим в доку:

Binary operations remove the top of the stack (TOS) and the second top-most stack item (TOS1) from the stack. They perform the operation, and put the result back on the stack.

BINARY_ADD()

Implements TOS = TOS1 + TOS.

In-place operations are like binary operations, in that they remove TOS and TOS1, and push the result back on the stack, but the operation is done in-place when TOS1 supports it, and the resulting TOS may be (but does not have to be) the original TOS1.

INPLACE_ADD()

Implements in-place TOS = TOS1 + TOS.

Поскольку числа не имеют метода __iadd__, отвечающего за in-place сложение, для них эти инструкции эквивалентны — отсюда и отсутствие просадки в производительности при использовании технически более сложной операции. Для более сложных объектов накладки неизбежны, поскольку итоговый TOS будет не новым объектом, а оригинальным TO1, изменяемым по месту.

Веселее только то что если предварительно сделать что-то типа npadd = np.add, то работать оно будет еще чуть быстрее, потому что np.add каждый раз берет сначала указатель на np, а затем на add()

>>> from dis import dis
>>> def foo():
...     for i in xrange(10):
...         baz.field
... 
>>> def bar():
...     foo = baz.field
...     for i in xrange(10):
...         foo
... 
>>> dis(foo)
  2           0 SETUP_LOOP              27 (to 30)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_CONST               1 (10)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                13 (to 29)
             16 STORE_FAST               0 (i)

  3          19 LOAD_GLOBAL              1 (baz)
             22 LOAD_ATTR                2 (field)
             25 POP_TOP             
             26 JUMP_ABSOLUTE           13
        >>   29 POP_BLOCK           
        >>   30 LOAD_CONST               0 (None)
             33 RETURN_VALUE        
>>> dis(bar)
  2           0 LOAD_GLOBAL              0 (baz)
              3 LOAD_ATTR                1 (field)
              6 STORE_FAST               0 (foo)

  3           9 SETUP_LOOP              24 (to 36)
             12 LOAD_GLOBAL              2 (xrange)
             15 LOAD_CONST               1 (10)
             18 CALL_FUNCTION            1
             21 GET_ITER            
        >>   22 FOR_ITER                10 (to 35)
             25 STORE_FAST               1 (i)

  4          28 LOAD_FAST                0 (foo)
             31 POP_TOP             
             32 JUMP_ABSOLUTE           22
        >>   35 POP_BLOCK           
        >>   36 LOAD_CONST               0 (None)
             39 RETURN_VALUE        
>>> 

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

Но что веселого или грустного понимать, как работает код(?)

Virtuos86 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.