[cython] разбор простой задачи

khachin

Решил тут поиграться со статической типизацией. Задачу взял .

#_randoms.py

from random import random
from datetime import datetime

def takeone(d):
rnd = random * sum(d.values
boundary = 0

for n in d:
boundary += d[n]
if rnd < boundary:
return n

chances = {1:10, 2:20, 3:15, 4:25, 5:30}
results = dictk, 0) for k in chances)

start = datetime.now

for i in range(10**7):
results[takeone(chances)] += 1

end = datetime.now

for k in results:
print round(100.0*chances[k]/sum(chances.values 2 round(100.0*results[k]/sum(results.values 2)
print 'Time Taken: %s'%(end-start)

Вышло нечто такое:

#randoms.pyx

from random import random
from datetime import datetime

def takeone(dict d):
cdef:
int n
float rnd = random * sum(d.values
int boundary = 0

for n in d:
boundary += d[n]
if rnd < boundary:
return n

cdef:
int i
int r = 10**7
dict chances, results

chances = {1:10, 2:20, 3:15, 4:25, 5:30}
results = dictk, 0) for k in chances)

start = datetime.now

for i in range(r):
results[takeone(chances)] += 1

end = datetime.now

for k in results:
print round(100.0*chances[k]/sum(chances.values 2 round(100.0*results[k]/sum(results.values 2)
print 'Time Taken: %s'%(end-start)

Запускаем в среде:
>>> import _randoms
10.0 9.99
20.0 20.0
15.0 14.97
25.0 25.01
30.0 30.03
Time Taken: 0:00:12.220000
>>> import randoms
10.0 10.0
20.0 20.01
15.0 14.99
25.0 25.0
30.0 30.01
Time Taken: 0:00:05.290000

Строк прибавилось на 7, время сократилось более, чем в 2 раза.
У кого есть опыт в "сатанизации"? Что еще можно улучшить конкретно в данном примере?
Оффтоп: На Galaxy Note 3 в qpython выполнение исходного скрипта заняло 0:01:09.640000

kiracher

Не такой чтобы уж спец по cpython/cython, но кое-что бросается в глаза. времени в обрез - так что только тезисно.
1) для такой задачи лучше подойдет numpy - с лету сказать сложно, но подходящий функционал в нем есть. в scipy возможно даже готовая функция найдется. для примера можно и самому проимплементировать - ок, идем дальше
2) зачем тут dict? это неупорядоченная структура для хранение разнотипных данных. тут больше уместен простой list - как по функциональности, так и по самому решению. с list этот же пример будет читаться лучше и выполняться быстрее.
3) ну ладно уж, взяли dict, идем дальше. а что дальше - пробегаемся по нему полностью (еще раз - это дорогая в смысле производительности структура данных) чтобы суммировать, а затем второй раз тоже до какого-то места и тоже суммируя! чтобы стало быстрее надо очевидно изменить алгоритм таким образом чтобы пробегаться было достаточно только один раз. посчитать как это влияет можно на примерах типа {1:1, 2:1, 3:1, 4:1, 5:95} {1:1, 2:1, 3:95, 4:1, 5:1} , {1:95, 2:1, 3:1, 4:1, 5:1}. Для какого то из этих примеров (насколько помню, dict не гарантирует порядка данных, поэтому нельзя сказать наперед для какого именно) второй цикл будет отрабатываться быстрей всего, для какого-то дольше всего. Из этой разницы можно оценить какой вклад вносит доступ к элементу dict в общее время выполнения.
так что общее направление оптимизации (если без применения сторонней библиотеки типа numpy/scipy) - это переписать с использованием list с одновременным уменьшением числа пробегов по нему. как именно - ну надо думать.
сам пример - скорей на тему как не стоит делать. этот якобы cython код на самом деле дергает очень много cpython, поэтому и переписывание дает мало. для полноты картины - сравнивать надо бы на больших объемах данных, а не только на большом количестве итераций.
и да - если я выше где-то облажался, прошу считать меня ни на что не претендующим ньюбом :o

khachin

Ну, объективно я со всем написанным согласен. dict вообще сам по себе тяжелый (было интересно, даст ли прирост скорости объявление dict — дало). Плюс я убрал сортировку ключей (скорость падала в два раза что не правильно (хоть и сработало верно).
Касательно улучшения алгоритмов и типов — это следующий шаг (сравнить через list/array, попробовать vector).
Итерации тут больше для:
первичное: растяжения времени, чтобы наглядно видеть прирост скорости выполнения,
вторичное: провести большую выборку, чтобы наглядно убедиться в вероятности выпадения при единичном вызове.
scipy/numpy тоже в планах (тем более cython с numpy вообще на "ты" на первом этапе интересны средства "из коробки".
Сейчас меня волнует, правильно ли я "сатанизировал" православный Python, т.к. раньше подобного (конкретно с ним) делал.

pilot

Запустил питоном - время 10 сек, запустил через pypy - 3.3 сек.
Строчек прибавилось 0, время сократилось в 3 раза :o

khachin

Тот же результат.
>>>> import _randoms
10.0 10.01
20.0 20.0
15.0 15.0
25.0 24.99
30.0 30.0
Time Taken: 0:00:03.480000

А статически типизированный код не завелся. Pypy скушал гигабайт памяти и прекратил работу.
Позже попробую более обдуманно собрать.

Dmitriy82

Мой опыт и с цитоном и со свигом весьма небольшой. Но я вот в упор не понимаю зачем нужен стрёмный диалект си с псевдопитоновым синтаксисом, когда есть нормальный си, а то и си++ и вполне себе mature SWIG чтобы к питону это присобачить. Единственное что приходит на ум - из цитона вроде как чуть проще дёргать питоновые сущности, ну и типа мигрировать постепенно. Но это очень редко же надо, в большинстве случаев performance-critical кусок находится внизу цепочки вызовов.
А если интерфейс простой, и обмен идёт только базовами типами, строками и сишными массивами, то вообще ничего не надо, есть ctypes.
Ну и как тут говорят если нужна не суперскорость а просто скорость, то можно pypy заюзать бесплатно.
Или если задача векторизуется то можно всё в numpy делать. И это не только к численным методам относится.
У цитона нет ниши.
Ы?

khachin

За время отпуска просто посетила мысль: какой будет прирост производительности, если провести статическую типизацию. Код написан, отлично работает. Будет ли профит, если задекларировать переменные, и есть ли готовые инструменты? Не я ж один такой умный, наверняка эта мысль посещала более творческие умы.
Гуглинг по фразе "static typed python" вывел на цитон.
Почитал доки, сел вчера написать хрень из первого поста. Безрассудная фигачилка типов дала прирост в два раза.
Как я к этому отношусь, пока не решил. Между 12 и 5 сек разница существенная, но между [math]$\dfrac{12}{10^7}$[/math] и [math]$\dfrac{5}{10^7}$[/math] не особо велика.
Нужно разобрать больше примеров, имеющих отношение к сфере деятельности, чтобы прийти к заключению. Но сперва надо понять, всё ли правильно объявлено тут или упустил чего.
И да, возможно, я нагуглил не тот инструмент для анализа.

zya369

Между 12 и 5 сек разница существенная, но между ... и ... не особо велика.
вообще-то одинаковая - в 2.4 раза

khachin

Не ясно выразился. В плане человеческого ожидания.

khachin

Отказ от dict
# _randoms.py

from random import random
from datetime import datetime

def takeone(l):
rnd = random * sum(l)
boundary = 0
for i in range(len(l:
boundary += l[i]
if rnd < boundary:
return i

values = ['ru','us','en','de','fr']
chances = [ 10, 20, 15, 25, 30 ]
results = [0 for i in chances]
r = 10**7

start = datetime.now

for i in range(r):
results[takeone(chances)] += 1

end = datetime.now

for i in range(len(chances:
print values[i], round(100.0*chances[i]/sum(chances 2 round(100.0*results[i]/sum(results 2)
print 'Time Taken: %s'%(end-start)

# randoms.pyx

from random import random
from datetime import datetime

def takeone(list l):
cdef:
int i
float rnd = random * sum(l)
int boundary = 0
for i in range(len(l:
boundary += l[i]
if rnd < boundary:
return i

cdef:
int i
list values = ['ru','us','en','de','fr']
list chances = [ 10, 20, 15, 25, 30 ]
list results = [0 for i in chances]
int r = 10**7

start = datetime.now

for i in range(r):
results[takeone(chances)] += 1

end = datetime.now

for i in range(len(chances:
print values[i], round(100.0*chances[i]/sum(chances 2 round(100.0*results[i]/sum(results 2)
print 'Time Taken: %s'%(end-start)

В православном время выполнения не поменялось, в собранном дало прирост 4х.
>>> import _randoms
ru 10.0 10.01
us 20.0 19.99
en 15.0 15.0
de 25.0 25.01
fr 30.0 29.99
Time Taken: 0:00:12.190000
>>> import randoms
ru 10.0 9.99
us 20.0 20.01
en 15.0 15.01
de 25.0 24.97
fr 30.0 30.01
Time Taken: 0:00:03.080000

pypy всех сделал.
>>>> import _randoms
ru 10.0 10.0
us 20.0 19.97
en 15.0 15.01
de 25.0 25.0
fr 30.0 30.02
Time Taken: 0:00:01.758000

Прошелся по Гуглу чуть ниже первых строк. Оказывается, сам Г. ван Россум думает от статической типизации.
Касательно цитона я так понял, основная (полезная) идея в доступе из Py2, Py3 и C. Пригодится, если появится необходимость перейти с версии 2 на 3.
А пока, да, достаточно pypy. Спасибо отозвавшимся.

pilot

У нас сейчас вычисления на питоне делаются через pypy+numpy.
Сишные куски мы пробовали делать пару лет назад, итог неутешителен - прирост производительности в пару раз, зато оптимизировать получающуюся кашу очень сложно.
cython в принципе не придумали зачем нужен.

asvsergey

Но я вот в упор не понимаю зачем нужен стрёмный диалект си с псевдопитоновым синтаксисом, когда есть нормальный си, а то и си++ и вполне себе mature SWIG чтобы к питону это присобачить.
Использовать cython для биндинга C кода проще и удобнее.
Оставить комментарий
Имя или ник:
Комментарий: