Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Ремизов Иван
Cloud Architect
Оптимизация
производительности Python
‹#›
Картинка для привлечения внимания
* CPython, без привлечения внешних зависимостей, компиляции и тп
30x
Сверхоптимизация кода на Python
‹#›
Python
Рассматриваем язык:
Python 2.x
Конкретные реализации:
CPython (*nix default)
PyPy (JIT)
‹#›
Проблемы приложения.
Какие проблемы вообще бывают?
• неудачные архитектурные решения
• неудачно выбранные компоненты и фреймворки
• медленный I/O
• высокий расход памяти, утечки памяти
• медленный код
‹#›
Проблемы приложения.
Как решается большинство проблем?
• добавление воркеров
• кеширование
• отложенные задания, очереди
• замена компонентов
• map/reduce
• изменение архитектуры
• …
‹#›
Когда это критично и не решаемо «привычными» способами?
Обработка потоковых данных
пример: процессинг датчиков (акселерометры, гироскопы)
Десериализация
пример: JSON, pickle, ..
Авторегрессия
пример: EMA (скользящая средняя), численное интегрирование,
ряды
Стейт-машины
пример: AI, синтаксические анализаторы текста
Медленный код.
‹#›
Профилирование специальными утилитами
• ручной профайлинг (тайминг)
• статистический профайлинг (сэмплинг)
• событийный профайлинг (граф вызовов)
Логгирование и сбор статистики
• настройка конфигов apache/nginx/…
• логи приложения
Как найти критические участки кода?
‹#›
Утилиты
• profile/cprofile
• pycallgraph
• dis (иногда бывает полезно)
Profiling.
‹#›
Выбор огромен
• line_profiler
• hotshot
• gprof2dot
• memory_profiler
• objgraph
• memprof
• для django есть миддлвары с картинками и
графиками
• django debug toolbar
• django live profiler
• …
Profiling.
‹#›
Задача: профилирование живого WEB-сервера
• мы не хотим чтобы профилировщик значительно снижал
производительность
• мы хотим получить более-менее репрезентативные данные
Решение:
1. поднять апстрим на ~1% и собирать статистику с него (*)
2. воспроизвести на стейджинге/тестовом окружении
Альтернатива:
• настраиваем access logs
• смотрим, где медленно
• разбираемся почему
Итого.
‹#›
• проводим серию испытаний
• замеряем среднее время
• исключаем I/O, профилировщик и тп
• помним про погрешность
• разогреваем JIT (* PyPy ~ 0.2c — см. доки)
• как-то используем результаты теста, иначе JIT может
его «вырезать»
• целевой пробег сопоставим по производительности с
разогревочным
• целевой пробег на JIT должен работать быстрее
Как правильно писать тесты на производительность?
‹#›
• Регрессионные тесты
• Не нужно делать гипотез и предположений: только
цифры
• Проблему с I/O исключили
• Первое что стоит оптимизировать — алгоритм
• Проблема скорее всего в каком-то из циклов
• Все статические переменные должны быть вынесены
из цикла
• eval, exec — плохо
• Не увлекаться!
О чем всегда помнить
‹#›
CPython — интерпретатор.
Он честно интерпретирует каждую строку кода.
• Lookup-ы — очень дороги
• атрибуты и методы
• локальные/глобальные переменные
• замыкание
• Запоминание переменных дорого
• Создание объектов — дорого
• Изменение размеров объектов в памяти — дорого
• eval, exec — плохо
Особенности присущие CPython
‹#›
PyPy использует JIT.
PyPy пытается исполнить то, что вы имели в виду
Исполняется совсем не тот код, который вы пишите.
• JIT scope != trace: locals(), globals(), sys._getframe(),
sys.exc_info(), sys.settrace, …
• На JIT компиляцию требуется время (>0.2s)
• => то, что «гоняется редко» — оптимизировано не
будет
• C-модули поддерживаются плохо: используем Python-
версию
• eval, exec — плохо
Особенности присущие PyPy
‹#›
ПРИМЕРЫ
‹#›
FizzBuzz
Для данного списка натуральный чисел (int) вернуть
строку со значениями через запятую, где
• числа, делящиеся на 3 заменены на "Fizz";
• числа, делящиеся на 5 заменены на "Buzz";
• числа, делящиеся одновременно и на 3, и на 5
заменены на "FizzBuzz";
• остальные числа выведены как есть.
Например:
[1, 2, 5, 15, 3, 1, 1, 4] => "1,2,Buzz,FizzBuzz,Fizz,1,1,4"
http://rosettacode.org/wiki/FizzBuzz
‹#›
FizzBuzz. Самое простое решение (Гуглим).
for i in xrange(1, 101):

if i % 15 == 0:

print "FizzBuzz"

elif i % 3 == 0:

print "Fizz"

elif i % 5 == 0:

print "Buzz"

else:

print i
‹#›
FizzBuzz. Самое простое решение.
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 15 == 0:

output_array.append("FizzBuzz")

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
FizzBuzz: Тесты
CORRECT_100 = (

"1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,"

"16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,"

"31,32,Fizz,34,Buzz,Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,"

"46,47,Fizz,49,Buzz,Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,"

"61,62,Fizz,64,Buzz,Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,"

"76,77,Fizz,79,Buzz,Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,"

"91,92,Fizz,94,Buzz,Fizz,97,98,Fizz,Buzz"

)
def check_correct_100(fn):

print 'checking function {fn.__name__}'.format(**locals()),

output = fn(range(1, 101))

if output == CORRECT_100:

print '.. ok'

else:

print ‘.. failed'
‹#›
FizzBuzz: Тайминг
import gc
import hashlib
import time
from random import shuffle





def _timetest(fn, n):

gc.disable()

gc.collect()



setup = [range(1, 101) for _ in xrange(n)]

map(shuffle, setup)

ts = time.clock()

output = map(fn, setup)

tt = time.clock() - ts

print '.. took {:.5f}s, for {} runs, avg={}ms hash={}'.format(

tt, n, tt * 1000 / n, hashlib.md5(''.join(output)).hexdigest())

gc.enable()


def check_time_taken(fn, n_warming=10000, n_executing=1000):

print 'checking function {fn.__name__} for speed'.format(**locals())

print 'warming up',

_timetest(fn, n_warming)



print 'executing',

_timetest(fn, n_executing)
‹#›
Инструменты
• Юнит-тесты или иной способ проверки правильности алгоритма
check_correct_100(fizzbuzz_simple)
• Замеры времени
check_time_taken(fizzbuzz_simple)
• Модуль dis
from dis import dis

dis(fizzbuzz_simple)
• Модуль Profile
from profile import run

run('fizzbuzz_simple(range(100000))')
• Утилита Pycallgraph
from pycallgraph import PyCallGraph

from pycallgraph.output import GraphvizOutput



with PyCallGraph(output=GraphvizOutput()):

fizzbuzz_simple(range(100000))
‹#›
Как выглядит вывод dis
4 0 BUILD_LIST 0
3 STORE_FAST 1 (output_array)
5 6 SETUP_LOOP 129 (to 138)
9 LOAD_FAST 0 (arr)
12 GET_ITER
>> 13 FOR_ITER 121 (to 137)
16 STORE_FAST 2 (i)
6 19 LOAD_FAST 2 (i)
22 LOAD_CONST 1 (15)
25 BINARY_MODULO
26 LOAD_CONST 2 (0)
29 COMPARE_OP 2 (==)
32 POP_JUMP_IF_FALSE 51
7 35 LOAD_FAST 1 (output_array)
38 LOAD_ATTR 0 (append)
41 LOAD_CONST 3 ('FizzBuzz')
44 CALL_FUNCTION 1
47 POP_TOP
48 JUMP_ABSOLUTE 13
8 >> 51 LOAD_FAST 2 (i)
54 LOAD_CONST 4 (3)
57 BINARY_MODULO
58 LOAD_CONST 2 (0)
61 COMPARE_OP 2 (==)
64 POP_JUMP_IF_FALSE 83
9 67 LOAD_FAST 1 (output_array)
70 LOAD_ATTR 0 (append)
73 LOAD_CONST 5 ('Fizz')
76 CALL_FUNCTION 1
79 POP_TOP
80 JUMP_ABSOLUTE 13
10 >> 83 LOAD_FAST 2 (i)
86 LOAD_CONST 6 (5)
89 BINARY_MODULO
90 LOAD_CONST 2 (0)
93 COMPARE_OP 2 (==)
96 POP_JUMP_IF_FALSE 115
11 99 LOAD_FAST 1 (output_array)
102 LOAD_ATTR 0 (append)
105 LOAD_CONST 7 ('Buzz')
108 CALL_FUNCTION 1
111 POP_TOP
112 JUMP_ABSOLUTE 13
13 >> 115 LOAD_FAST 1 (output_array)
118 LOAD_ATTR 0 (append)
121 LOAD_GLOBAL 1 (str)
124 LOAD_FAST 2 (i)
127 CALL_FUNCTION 1
130 CALL_FUNCTION 1
133 POP_TOP
134 JUMP_ABSOLUTE 13
>> 137 POP_BLOCK
14 >> 138 LOAD_CONST 8 (',')
141 LOAD_ATTR 2 (join)
144 LOAD_FAST 1 (output_array)
147 CALL_FUNCTION 1
150 RETURN_VALUE
4 0 BUILD_LIST 0
3 STORE_FAST 1 (output_array)
5 6 SETUP_LOOP 129 (to 138)
9 LOAD_FAST 0 (arr)
12 GET_ITER
>> 13 FOR_ITER 121 (to 137)
16 STORE_FAST 2 (i)
6 19 LOAD_FAST 2 (i)
22 LOAD_CONST 1 (15)
25 BINARY_MODULO
26 LOAD_CONST 2 (0)
29 COMPARE_OP 2 (==)
32 POP_JUMP_IF_FALSE 51
7 35 LOAD_FAST 1 (output_array)
38 LOAD_ATTR 0 (append)
41 LOAD_CONST 3 ('FizzBuzz')
44 CALL_FUNCTION 1
47 POP_TOP
48 JUMP_ABSOLUTE 13
. . .
‹#›
Как выглядит вывод профайлера
100006 function calls in 0.699 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
100000 0.302 0.000 0.302 0.000 :0(append)
1 0.003 0.003 0.003 0.003 :0(join)
1 0.003 0.003 0.003 0.003 :0(range)
1 0.002 0.002 0.002 0.002 :0(setprofile)
1 0.002 0.002 0.697 0.697 <string>:1(<module>)
1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple)
1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000)))
0 0.000 0.000 profile:0(profiler)
‹#›
Как выглядит вывод профайлера
100006 function calls in 0.699 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
100000 0.302 0.000 0.302 0.000 :0(append)
1 0.003 0.003 0.003 0.003 :0(join)
1 0.003 0.003 0.003 0.003 :0(range)
1 0.002 0.002 0.002 0.002 :0(setprofile)
1 0.002 0.002 0.697 0.697 <string>:1(<module>)
1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple)
1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000)))
0 0.000 0.000 profile:0(profiler)
Проблемный
участок
‹#›
Как выглядит вывод профайлера
100006 function calls in 0.699 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
100000 0.302 0.000 0.302 0.000 :0(append)
1 0.003 0.003 0.003 0.003 :0(join)
1 0.003 0.003 0.003 0.003 :0(range)
1 0.002 0.002 0.002 0.002 :0(setprofile)
1 0.002 0.002 0.697 0.697 <string>:1(<module>)
1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple)
1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000)))
0 0.000 0.000 profile:0(profiler)
Артефакт
‹#›
Как выглядит вывод PyCallGraph
‹#›
FizzBuzz. eval.
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 15 == 0:

eval(
'output_array.append("FizzBuzz")',
globals(), locals())

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
FizzBuzz. exec.
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 15 == 0:

(exec
‘output_array.append("FizzBuzz")')

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
FizzBuzz: OOP.
ArrayProcessor
Replacer
ItemProcessor
Array of items
Replacer
Replacer
processed as string
‹#›
FizzBuzz: OOP.
class AbstractReplacer(object):

__metaclass__ = ABCMeta

__slots__ = 'value', 'output'

return_value = NotImplemented



def __init__(self, value):

pass



@abstractmethod

def validate_input(self):

raise NotImplementedError



@abstractmethod

def check_match(self):

raise NotImplementedError



@abstractmethod

def process(self):

raise NotImplementedError



@abstractmethod

def get_output_value(self):

raise NotImplementedError

‹#›
FizzBuzz: OOP.
class AbstractItemProcessor(object):

__metaclass__ = ABCMeta

__slots__ = 'value', 'output'

replacer_classes = NotImplemented



def __init__(self, value):

pass



@abstractmethod

def validate_input(self):

raise NotImplementedError



@abstractmethod

def validate_processed_value(self):

raise NotImplementedError



@abstractmethod

def process(self):

raise NotImplementedError



@abstractmethod

def get_replacer_classes(self):

raise NotImplementedError



@abstractmethod

def get_output_value(self):

raise NotImplementedError

‹#›
FizzBuzz: OOP.
class AbstractArrayProcessor(object):

__metaclass__ = ABCMeta

__slots__ = 'array', 'output'

item_processer_class = NotImplemented



def __init__(self, array):

pass



@abstractmethod

def validate_input(self):

raise NotImplementedError



@abstractmethod

def process(self):

raise NotImplementedError



@abstractmethod

def get_item_processer_class(self):

raise NotImplementedError



@abstractmethod

def get_output_value(self):

raise NotImplementedError

‹#›
FizzBuzz: OOP.
class ImproperInputValue(Exception):

pass





class ImproperOutputValue(Exception):

pass

‹#›
FizzBuzz: OOP.
class BaseReplacer(AbstractReplacer):

return_value = None

divider = 1



def __init__(self, value):

super(BaseReplacer, self).__init__(value)

self.value = value

self.validate_input()

self.output = None



def validate_input(self):

if not isinstance(self.value, int):

raise ImproperInputValue(self.value)



def check_match(self):

return self.value % self.divider == 0



def process(self):

if self.check_match():

self.output = self.return_value



def get_output_value(self):

return self.output



‹#›
FizzBuzz: OOP.
class BaseItemProcessor(AbstractItemProcessor):

replacer_classes = BaseReplacer,



def __init__(self, value):

super(BaseItemProcesser, self).__init__(value)

self.value = value

self.validate_input()

self.output = None



def validate_input(self):

if not isinstance(self.value, int):

raise ImproperInputValue(self.value)



def validate_processed_value(self):

if not isinstance(self.output, basestring):

raise ImproperOutputValue



def process(self):

for replacer_class in self.get_replacer_classes():

replacer = replacer_class(self.value)

replacer.process()

processed_value = replacer.get_output_value()

if processed_value is not None:

self.output = processed_value

break



def get_replacer_classes(self):

return self.replacer_classes



def get_output_value(self):

return self.output

‹#›
FizzBuzz: OOP.
class BaseArrayProcessor(AbstractArrayProcessor):

item_processor_class = BaseItemProcessor



def __init__(self, array):

super(BaseArrayProcessor, self).__init__(array)

self.array = array

self.validate_input()

self.output = ''



def validate_input(self):

if not isinstance(self.array, (list, tuple, set)):

raise ImproperInputValue(self.array)



def process(self):

output_array = []

for item in self.array:

item_processor_class = self.get_item_processor_class()

item_processor = item_processor_class(item)

item_processor.process()

processed_item = item_processor.get_output_value()

if processed_item:

output_array.append(processed_item)

self.output = ','.join(output_array)



def get_item_processor_class(self):

return self.item_processor_class



def get_output_value(self):

return self.output



‹#›
FizzBuzz: OOP.
FIZZ = "Fizz"

BUZZ = "Buzz"

FIZZBUZZ = FIZZ + BUZZ





class MultiplesOfThreeReplacer(BaseReplacer):

return_value = FIZZ

divider = 3





class MultiplesOfFiveReplacer(BaseReplacer):

return_value = BUZZ

divider = 5





class MultiplesOfThreeAndFiveReplacer(BaseReplacer):

return_value = FIZZBUZZ

divider = 15





class IntToStrReplacer(BaseReplacer):

def check_match(self):

return True



def process(self):

self.output = str(self.value)



‹#›
FizzBuzz: OOP.


class FizzBuzzItemProcessor(BaseItemProcessor):

replacer_classes = (

MultiplesOfThreeAndFiveReplacer,

MultiplesOfThreeReplacer,

MultiplesOfFiveReplacer,

IntToStrReplacer,

)





class FizzBuzzProcessor(BaseArrayProcessor):

item_processor_class = FizzBuzzItemProcessor





def fizzbuzz_oop(arr):

fbp = FizzBuzzProcessor(arr)

fbp.process()

return fbp.get_output_value()

‹#›
ЗАМЕРЫ
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 1х
FizzBuzz
simple
Adding eval
Adding exec
FizzBuzz
optimized
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
Adding eval
Adding exec
FizzBuzz
optimized
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval
Adding exec
FizzBuzz
optimized
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval 3,49037 6,34854 6,9х 3,8x
Adding exec
FizzBuzz
optimized
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval 3,49037 6,34854 6,9х 3,8x
Adding exec 3,90273 — 6х —
FizzBuzz
optimized
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval 3,49037 6,34854 6,9х 3,8x
Adding exec 3,90273 — 6х —
FizzBuzz
optimized
? ? ? ?
‹#›
FizzBuzz: OOP. PyCallGraph
‹#›
FizzBuzz: OOP. PyCallGraph
Самые ресурсоемкие вызовы
‹#›
О ПРЕЖДЕВРЕМЕННОЙ ОПТИМИЗАЦИИ
‹#›
Оптимизация алгоритма
Для данного списка натуральный чисел (int) вернуть
строку со значениями через запятую, где
• числа, делящиеся на 3 заменены на "Fizz";
• числа, делящиеся на 5 заменены на "Buzz";
• числа, делящиеся одновременно и на 3, и на 5
заменены на "FizzBuzz";
• остальные числа выведены как есть.
Например:
[1, 2, 5, 15, 3, 1, 1, 4] => "1,2,Buzz,FizzBuzz,Fizz,1,1,4"
http://rosettacode.org/wiki/FizzBuzz
‹#›
Оптимизация алгоритма
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 15 == 0:

output_array.append("FizzBuzz")

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
15?
‹#›
Оптимизация алгоритма
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 3 == 0 and i % 5 == 0:

output_array.append("FizzBuzz")

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизация алгоритма
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 3 == 0 and i % 5 == 0:

output_array.append("FizzBuzz")

elif i % 3 == 0:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизация алгоритма
def fizzbuzz_simple(arr):

output_array = []

for i in arr:
if i % 3 == 0:

if i % 5 == 0:

output_array.append("FizzBuzz")

else:

output_array.append("Fizz")

elif i % 5 == 0:

output_array.append("Buzz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизация алгоритма
Количество сравнений для списка значений 1 .. 15
До … 39
После … 30
По времени ~ 3% разницы
По количеству операций ~ 30%
А что если переставить порядок сравнений?
‹#›
Оптимизация алгоритма. Перестановка операций
def fizzbuzz_simple(arr):

output_array = []

for i in arr:

if i % 15 == 0:

output_array.append("FizzBuzz")

elif i % 5 == 0:

output_array.append("Buzz")

elif i % 3 == 0:

output_array.append("Fizz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизация алгоритма. Перестановка операций
def fizzbuzz_simple(arr):

output_array = []

for i in arr:
if i % 5 == 0:

if i % 3 == 0:

output_array.append("FizzBuzz")

else:

output_array.append("Buzz")

elif i % 3 == 0:

output_array.append("Fizz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизация алгоритма. Перестановка операций
Количество сравнений для списка значений 1 .. 15
Плохой вариант
До … 39
После … 41 (хуже)
Улучшенный вариант
До … 30
После … 30 (не изменилось)
От лучшего до худшего ~ 30%
‹#›
ОПТИМИЗИРУЕМ CPYTHON
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval 3,49037 6,34854 6,9х 3,8x
Adding exec 3,90273 — 6х —
FizzBuzz
optimized
? ? ? ?
‹#›
Оптимизируем CPython. Lookup
def fizzbuzz_simple(arr):

output_array = []

for i in arr:
if i % 5 == 0:

if i % 3 == 0:

output_array.append("FizzBuzz")

else:

output_array.append("Buzz")

elif i % 3 == 0:

output_array.append("Fizz")

else:

output_array.append(str(i))

return ",".join(output_array)
‹#›
Оптимизируем CPython. Lookup
def fizzbuzz_simple(arr):

output_array = []
_append = output_array.append

for i in arr:
if i % 5 == 0:

if i % 3 == 0:

_append(«FizzBuzz")

else:

_append(«Buzz")

elif i % 3 == 0:

_append(«Fizz")

else:

_append(str(i))

return ",".join(output_array)
‹#›
Оптимизируем CPython. Lookup
def fizzbuzz_simple(arr):

output_array = []
_append = output_array.append

for i in arr:
if i % 5 == 0:

if i % 3 == 0:

_append(«FizzBuzz")

else:

_append(«Buzz")

elif i % 3 == 0:

_append(«Fizz")

else:

_append(str(i))

return ",".join(output_array) 1.3x
‹#›
FizzBuzz: Тесты
CORRECT_100 = (

"1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,"

"16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,"

"31,32,Fizz,34,Buzz,Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,"

"46,47,Fizz,49,Buzz,Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,"

"61,62,Fizz,64,Buzz,Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,"

"76,77,Fizz,79,Buzz,Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,"

"91,92,Fizz,94,Buzz,Fizz,97,98,Fizz,Buzz"

)
def check_correct_100(fn):

print 'checking function {fn.__name__}'.format(**locals()),

output = fn(range(1, 101))

if output == CORRECT_100:

print '.. ok'

else:

print ‘.. failed'
‹#›
Быстрый FizzBuzz
def fizzbuzz_samples_helper(arr):

for i in arr:

if i % 3 == 0:

if i % 5 == 0:

yield "FizzBuzz"

else:

yield "Fizz"

elif i % 5 == 0:

yield "Buzz"

else:

yield False





samples = tuple(fizzbuzz_samples_helper(xrange(15)))
‹#›
FizzBuzz. Перестановка операций
samples = (False, False, «Fizz" ,False,
«Buzz", . . . , "FizzBuzz")
def fizzbuzz(arr):

output_array = [
samples[i % 15] or str(i) for i in arr]

return ",".join(output_array)
‹#›
FizzBuzz. Перестановка операций
samples = (False, False, «Fizz" ,False,
«Buzz", . . . , "FizzBuzz")
def fizzbuzz(arr):

output_array = [
samples[i % 15] or str(i) for i in arr]

return ",".join(output_array)
1,35x
‹#›
Быстрый FizzBuzz


def fizzbuzz_with_precached_samples(

arr,

# shorteners
__join=",".join, 

__samples=samples,

__str=str

):

return __join(__samples[i % 15] or __str(i) for i in arr)
‹#›
Быстрый FizzBuzz


def fizzbuzz_with_precached_samples(

arr,

# shorteners
__join=",".join, 

__samples=samples,

__str=str

):

return __join(__samples[i % 15] or __str(i) for i in arr)
0,96x ?
‹#›
FizzBuzz: Результаты
cpython pypy cpython
to FizzBuzz
OOP
cpython
pypy
to FizzBuzz
OOP
cpythonFizzBuzz
OOP
24,11218 0,72933 1х 33x
FizzBuzz
simple
1,23326 0,23751 19,5х 101х
Adding eval 3,49037 6,34854 6,9х 3,8x
Adding exec 3,90273 — 6х —
FizzBuzz
optimized
0,72047 0,24492 33,4x 101x
‹#›
ПРОДВИНУТЫЕ ПОДХОДЫ
‹#›
СОПРОЦЕСС / COROUTINE
‹#›
Coroutines
64 0 LOAD_FAST 1 (__join)
3 LOAD_CLOSURE 0 (__samples)
6 LOAD_CLOSURE 1 (__str)
9 BUILD_TUPLE 2
12 LOAD_CONST 1 (<code object
<genexpr> at 0x10d849930, file "./___.py", line 64>)
15 MAKE_CLOSURE 0
18 LOAD_FAST 0 (arr)
21 GET_ITER
22 CALL_FUNCTION 1
25 CALL_FUNCTION 1
28 RETURN_VALUE
‹#›
Coroutines
def fizzbuzz_co(

# shorteners

__join=",".join,

__samples=samples,

__str=str

):

arr = ()

while True:

arr = yield __join(__samples[i % 15] or __str(i) for i in arr)



_ = fizzbuzz_co()

_.next()

fizzbuzz_co= _.send
‹#›
Coroutines
def fizzbuzz_co(

# shorteners

__join=",".join,

__samples=samples,

__str=str

):

arr = ()

while True:

arr = yield __join(__samples[i % 15] or __str(i) for i in arr)





_ = fizzbuzz_co()

_.next()

fizzbuzz_co= _.send
outputinput
output = co.send(input)
«инициализировать» и
получить первый output
создать сопроцесс
заменить ссылку на метод send
‹#›
def co():
. . .
x = yield y
[return None]
c = co()
out = c.send(Z)
Coroutines
Как это работает
• def + yield = ключевые слова
• создаем «конструктор» генератора
• вызов c = co() создает генератор c
• c.next()
• выполнит все до первого yield,
• вернет результат выражения y,
• «встанет на паузу»
• c.send(Z)
• x = Z
• продолжит выполнение до yield/return
• out = y
• return завершает выполнение (StopIteration)
‹#›
Coroutines
Можно обернуть в декоратор:


def coroutine(fn):

_ = fn()

_.next()

return _.send
‹#›
Coroutines
… и поместить все внутрь (до первого yield)
@coroutine

def fizzbuzz_co():

def fizzbuzz_samples_helper(arr):

for i in arr:

if i % 3 == 0:

if i % 5 == 0:

yield "FizzBuzz"

else:

yield "Fizz"

elif i % 5 == 0:

yield "Buzz"

else:

yield False



__join = ",".join

__str = str

samples = tuple(fizzbuzz_samples_helper(xrange(15)))

arr = ()



while True:

arr = yield __join(samples[i % 15] or __str(i) for i in arr)
‹#›
КЕШИРУЮЩИЕ ФУНКЦИИ
‹#›
Быстрый FizzBuzz, кэширующая функция
Кэширующая функция
• вычисления ресурсоемки
• значения аргументов часто повторяются
def cached(fn):

cache = {}



@wraps(fn)

def decorated(arg):

value = cache.get(arg)

if not value:

cache[arg] = value = fn(arg)

return value



return decorated
‹#›
Быстрый FizzBuzz, кэширующая функция
@cached

def process_one(

i,

# shorteners

__samples=samples,

__str=str

):

return __samples[i % 15] or __str(i)

def fizzbuzz_with_cache(

arr,

# shorteners

__join=",".join,

):

return __join(map(process_one, arr))
‹#›
COROUTINE-BASED CLASS
‹#›
class MakeSum(Exception):

pass





class ChgKoef(Exception):

pass





def co():

x = None

y = None

k = 1

rv = None



while True:

try:

x, y = yield rv

rv = k * x * y

except MakeSum as e:

x, y = e.args

rv = k * (x + y)

except ChgKoef as e:

k = e.args[0]
Coroutine based class
class Cls(object):

def __init__(self):

self.x = None

self.y = None

self.k = 1

self.rv = None



def main_method(self, x, y):

self.x = x

self.y = y

self.rv = self.k * self.x * self.y

return self.rv



def make_sum(self, x, y):

self.x = x

self.y = y

self.rv = self.k * (self.x + self.y)

return self.rv



def chg_koef(self, k):

self.k = k

return self.rv
‹#›
instance = co()

print instance

# <generator object co at 0x10047bbe0>



print instance.next()

# None



print instance.send((1, 2))

# 2 == 1 * 1 * 2



print instance.send((3, 4))

# 12 == 1 * 3 * 4



print instance.throw(MakeSum(5, 6))

# 11 == 1 * (5 + 6)



print instance.send((7, 8))

# 56 == 1 * 7 * 8



print instance.throw(ChgKoef(10))

# 56 (last value repeated)



print instance.send((1, 2))

# 20 == 10 * 1 * 2
Coroutine based inheritance
instance = Cls()

print instance

# <__main__.Cls object at 0x10a1c6210>



print instance.main_method(1, 2)

# 2 == 1 * 1 * 2



print instance.main_method(3, 4)

# 12 == 1 * 3 * 4



print instance.make_sum(5, 6)

# 11 == 1 * (5 + 6)



print instance.main_method(7, 8)

# 56 == 1 * 7 * 8



print instance.chg_koef(10)

# 56 (last value repeated)



print instance.main_method(1, 2)

# 20 == 10 * 1 * 2
‹#›
COROUTINE-BASED INHERITANCE
‹#›
def co(

param,

# ___

__some_value=5,

__some_method=lambda: 10

):

rv = None



while True:

input = yield rv



def co_sub(

param,

# ___

__some_value=10,

__some_method=lambda: 20

):

return co(**locals())
Coroutine based class
class Cls(object):

def __init__(self, param):

self.param = param



some_value = 5



def some_method(self):

return 10



def main_method(self):

return





class SubCls(Cls):

some_value = 10



def some_method(self):

return 20
‹#›
Coroutine based class
coroutine class coroutine vs
class
send
main method
4,23 6,93 1,63x faster
throw MakeSum
make_sum
21,85 7,30 3x slower
‹#›
Coroutine based class
Плюсы
• Основной метод работает быстрее
• «Наследование»
Минусы
• Интерфейс «заморожен»
• Основной метод «заморожен»
• Код «специфичен»
‹#›
«ЧИСЛОДРОБИЛКИ»
‹#›
Cython, numpy, weave, etc..
«Числодробилки»
Travis Oliphant
from numpy import zeros

from scipy import weave



dx = 0.1

dy = 0.1

dx2 = dx*dx

dy2 = dy*dy



def py_update(u):

nx, ny = u.shape

for i in xrange(1,nx-1):

for j in xrange(1, ny-1):

u[i,j] = ((u[i+1, j] + u[i-1, j]) * dy2 +

(u[i, j+1] + u[i, j-1]) * dx2) / (2*(dx2+dy2))



def calc(N, Niter=100, func=py_update, args=()):

u = zeros([N, N])

u[0] = 1

for i in range(Niter):

func(u,*args)

return u
‹#›
Почти тот же Python!


cimport numpy as np



def cy_update(np.ndarray[double, ndim=2] u, double dx2, double dy2):

cdef unsigned int i, j

for i in xrange(1,u.shape[0]-1):

for j in xrange(1, u.shape[1]-1):

u[i,j] = ((u[i+1, j] + u[i-1, j]) * dy2 +

(u[i, j+1] + u[i, j-1]) * dx2) / (2*(dx2+dy2))

Cython, numpy, weave, etc..
‹#›
Cython, numpy, weave, etc..
Почти «чистый С»
def weave_update(u):

code = """

int i, j;

for (i=1; i<Nu[0]-1; i++) {

for (j=1; j<Nu[1]-1; j++) {

U2(i,j) = ((U2(i+1, j) + U2(i-1, j))*dy2 + 

(U2(i, j+1) + U2(i, j-1))*dx2) / (2*(dx2+dy2));

}

}

"""

weave.inline(code, ['u', 'dx2', 'dy2'])
‹#›
Cython, numpy, weave, etc..
Method Time (sec) relative speed
(меньше-лучше)
Pure python 560 250
NumPy 2,24 1
Cython 1,28 0,51
Weave 1,02 0,45
Faster Cython 0,94 0,42
‹#›
РЕЦЕПТ
‹#›
Рецепт
• найти слабое место
• убедиться что все упирается в производительность кода, а не в
дисковое/сетевое IO
• упростить ООП до простых функций и процедур
• оптимизировать алгоритм
• избавиться от лишних переменных
• избавиться от конструкций object.method()
• использовать итераторы/генераторы вместо списков
• завернуть все в сопроцессы
• постоянно замерять производительность на данных, схожих с
реальными
• тестировать
• знать когда остановиться
‹#›
• Ссылки, литература:
• Дэвид Бизли: генераторы/сопроцессы http://www.dabeaz.com/generators/
• Python и память http://www.slideshare.net/PiotrPrzymus/pprzymus-europython-2014
• Другой пример о профилировали — числа фибоначчи http://pymotw.com/2/profile/
• Про объекты, ссылки и утечки памяти http://mg.pov.lt/objgraph/
• line_profiler, memory_profiler http://www.huyng.com/posts/python-
performance-analysis/
• numpy, cython, weave http://technicaldiscovery.blogspot.ru/2011/06/speeding-up-
python-numpy-cython-and.html
• google
• Контакты:
• email: iremizov@parallels.com #CodeFest
• twitter: @iremizov

More Related Content

Сверхоптимизация кода на Python

  • 2. ‹#› Картинка для привлечения внимания * CPython, без привлечения внешних зависимостей, компиляции и тп 30x
  • 4. ‹#› Python Рассматриваем язык: Python 2.x Конкретные реализации: CPython (*nix default) PyPy (JIT)
  • 5. ‹#› Проблемы приложения. Какие проблемы вообще бывают? • неудачные архитектурные решения • неудачно выбранные компоненты и фреймворки • медленный I/O • высокий расход памяти, утечки памяти • медленный код
  • 6. ‹#› Проблемы приложения. Как решается большинство проблем? • добавление воркеров • кеширование • отложенные задания, очереди • замена компонентов • map/reduce • изменение архитектуры • …
  • 7. ‹#› Когда это критично и не решаемо «привычными» способами? Обработка потоковых данных пример: процессинг датчиков (акселерометры, гироскопы) Десериализация пример: JSON, pickle, .. Авторегрессия пример: EMA (скользящая средняя), численное интегрирование, ряды Стейт-машины пример: AI, синтаксические анализаторы текста Медленный код.
  • 8. ‹#› Профилирование специальными утилитами • ручной профайлинг (тайминг) • статистический профайлинг (сэмплинг) • событийный профайлинг (граф вызовов) Логгирование и сбор статистики • настройка конфигов apache/nginx/… • логи приложения Как найти критические участки кода?
  • 9. ‹#› Утилиты • profile/cprofile • pycallgraph • dis (иногда бывает полезно) Profiling.
  • 10. ‹#› Выбор огромен • line_profiler • hotshot • gprof2dot • memory_profiler • objgraph • memprof • для django есть миддлвары с картинками и графиками • django debug toolbar • django live profiler • … Profiling.
  • 11. ‹#› Задача: профилирование живого WEB-сервера • мы не хотим чтобы профилировщик значительно снижал производительность • мы хотим получить более-менее репрезентативные данные Решение: 1. поднять апстрим на ~1% и собирать статистику с него (*) 2. воспроизвести на стейджинге/тестовом окружении Альтернатива: • настраиваем access logs • смотрим, где медленно • разбираемся почему Итого.
  • 12. ‹#› • проводим серию испытаний • замеряем среднее время • исключаем I/O, профилировщик и тп • помним про погрешность • разогреваем JIT (* PyPy ~ 0.2c — см. доки) • как-то используем результаты теста, иначе JIT может его «вырезать» • целевой пробег сопоставим по производительности с разогревочным • целевой пробег на JIT должен работать быстрее Как правильно писать тесты на производительность?
  • 13. ‹#› • Регрессионные тесты • Не нужно делать гипотез и предположений: только цифры • Проблему с I/O исключили • Первое что стоит оптимизировать — алгоритм • Проблема скорее всего в каком-то из циклов • Все статические переменные должны быть вынесены из цикла • eval, exec — плохо • Не увлекаться! О чем всегда помнить
  • 14. ‹#› CPython — интерпретатор. Он честно интерпретирует каждую строку кода. • Lookup-ы — очень дороги • атрибуты и методы • локальные/глобальные переменные • замыкание • Запоминание переменных дорого • Создание объектов — дорого • Изменение размеров объектов в памяти — дорого • eval, exec — плохо Особенности присущие CPython
  • 15. ‹#› PyPy использует JIT. PyPy пытается исполнить то, что вы имели в виду Исполняется совсем не тот код, который вы пишите. • JIT scope != trace: locals(), globals(), sys._getframe(), sys.exc_info(), sys.settrace, … • На JIT компиляцию требуется время (>0.2s) • => то, что «гоняется редко» — оптимизировано не будет • C-модули поддерживаются плохо: используем Python- версию • eval, exec — плохо Особенности присущие PyPy
  • 17. ‹#› FizzBuzz Для данного списка натуральный чисел (int) вернуть строку со значениями через запятую, где • числа, делящиеся на 3 заменены на "Fizz"; • числа, делящиеся на 5 заменены на "Buzz"; • числа, делящиеся одновременно и на 3, и на 5 заменены на "FizzBuzz"; • остальные числа выведены как есть. Например: [1, 2, 5, 15, 3, 1, 1, 4] => "1,2,Buzz,FizzBuzz,Fizz,1,1,4" http://rosettacode.org/wiki/FizzBuzz
  • 18. ‹#› FizzBuzz. Самое простое решение (Гуглим). for i in xrange(1, 101):
 if i % 15 == 0:
 print "FizzBuzz"
 elif i % 3 == 0:
 print "Fizz"
 elif i % 5 == 0:
 print "Buzz"
 else:
 print i
  • 19. ‹#› FizzBuzz. Самое простое решение. def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 15 == 0:
 output_array.append("FizzBuzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 20. ‹#› FizzBuzz: Тесты CORRECT_100 = (
 "1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,"
 "16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,"
 "31,32,Fizz,34,Buzz,Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,"
 "46,47,Fizz,49,Buzz,Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,"
 "61,62,Fizz,64,Buzz,Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,"
 "76,77,Fizz,79,Buzz,Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,"
 "91,92,Fizz,94,Buzz,Fizz,97,98,Fizz,Buzz"
 ) def check_correct_100(fn):
 print 'checking function {fn.__name__}'.format(**locals()),
 output = fn(range(1, 101))
 if output == CORRECT_100:
 print '.. ok'
 else:
 print ‘.. failed'
  • 21. ‹#› FizzBuzz: Тайминг import gc import hashlib import time from random import shuffle
 
 
 def _timetest(fn, n):
 gc.disable()
 gc.collect()
 
 setup = [range(1, 101) for _ in xrange(n)]
 map(shuffle, setup)
 ts = time.clock()
 output = map(fn, setup)
 tt = time.clock() - ts
 print '.. took {:.5f}s, for {} runs, avg={}ms hash={}'.format(
 tt, n, tt * 1000 / n, hashlib.md5(''.join(output)).hexdigest())
 gc.enable() 
 def check_time_taken(fn, n_warming=10000, n_executing=1000):
 print 'checking function {fn.__name__} for speed'.format(**locals())
 print 'warming up',
 _timetest(fn, n_warming)
 
 print 'executing',
 _timetest(fn, n_executing)
  • 22. ‹#› Инструменты • Юнит-тесты или иной способ проверки правильности алгоритма check_correct_100(fizzbuzz_simple) • Замеры времени check_time_taken(fizzbuzz_simple) • Модуль dis from dis import dis
 dis(fizzbuzz_simple) • Модуль Profile from profile import run
 run('fizzbuzz_simple(range(100000))') • Утилита Pycallgraph from pycallgraph import PyCallGraph
 from pycallgraph.output import GraphvizOutput
 
 with PyCallGraph(output=GraphvizOutput()):
 fizzbuzz_simple(range(100000))
  • 23. ‹#› Как выглядит вывод dis 4 0 BUILD_LIST 0 3 STORE_FAST 1 (output_array) 5 6 SETUP_LOOP 129 (to 138) 9 LOAD_FAST 0 (arr) 12 GET_ITER >> 13 FOR_ITER 121 (to 137) 16 STORE_FAST 2 (i) 6 19 LOAD_FAST 2 (i) 22 LOAD_CONST 1 (15) 25 BINARY_MODULO 26 LOAD_CONST 2 (0) 29 COMPARE_OP 2 (==) 32 POP_JUMP_IF_FALSE 51 7 35 LOAD_FAST 1 (output_array) 38 LOAD_ATTR 0 (append) 41 LOAD_CONST 3 ('FizzBuzz') 44 CALL_FUNCTION 1 47 POP_TOP 48 JUMP_ABSOLUTE 13 8 >> 51 LOAD_FAST 2 (i) 54 LOAD_CONST 4 (3) 57 BINARY_MODULO 58 LOAD_CONST 2 (0) 61 COMPARE_OP 2 (==) 64 POP_JUMP_IF_FALSE 83 9 67 LOAD_FAST 1 (output_array) 70 LOAD_ATTR 0 (append) 73 LOAD_CONST 5 ('Fizz') 76 CALL_FUNCTION 1 79 POP_TOP 80 JUMP_ABSOLUTE 13 10 >> 83 LOAD_FAST 2 (i) 86 LOAD_CONST 6 (5) 89 BINARY_MODULO 90 LOAD_CONST 2 (0) 93 COMPARE_OP 2 (==) 96 POP_JUMP_IF_FALSE 115 11 99 LOAD_FAST 1 (output_array) 102 LOAD_ATTR 0 (append) 105 LOAD_CONST 7 ('Buzz') 108 CALL_FUNCTION 1 111 POP_TOP 112 JUMP_ABSOLUTE 13 13 >> 115 LOAD_FAST 1 (output_array) 118 LOAD_ATTR 0 (append) 121 LOAD_GLOBAL 1 (str) 124 LOAD_FAST 2 (i) 127 CALL_FUNCTION 1 130 CALL_FUNCTION 1 133 POP_TOP 134 JUMP_ABSOLUTE 13 >> 137 POP_BLOCK 14 >> 138 LOAD_CONST 8 (',') 141 LOAD_ATTR 2 (join) 144 LOAD_FAST 1 (output_array) 147 CALL_FUNCTION 1 150 RETURN_VALUE 4 0 BUILD_LIST 0 3 STORE_FAST 1 (output_array) 5 6 SETUP_LOOP 129 (to 138) 9 LOAD_FAST 0 (arr) 12 GET_ITER >> 13 FOR_ITER 121 (to 137) 16 STORE_FAST 2 (i) 6 19 LOAD_FAST 2 (i) 22 LOAD_CONST 1 (15) 25 BINARY_MODULO 26 LOAD_CONST 2 (0) 29 COMPARE_OP 2 (==) 32 POP_JUMP_IF_FALSE 51 7 35 LOAD_FAST 1 (output_array) 38 LOAD_ATTR 0 (append) 41 LOAD_CONST 3 ('FizzBuzz') 44 CALL_FUNCTION 1 47 POP_TOP 48 JUMP_ABSOLUTE 13 . . .
  • 24. ‹#› Как выглядит вывод профайлера 100006 function calls in 0.699 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 100000 0.302 0.000 0.302 0.000 :0(append) 1 0.003 0.003 0.003 0.003 :0(join) 1 0.003 0.003 0.003 0.003 :0(range) 1 0.002 0.002 0.002 0.002 :0(setprofile) 1 0.002 0.002 0.697 0.697 <string>:1(<module>) 1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple) 1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000))) 0 0.000 0.000 profile:0(profiler)
  • 25. ‹#› Как выглядит вывод профайлера 100006 function calls in 0.699 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 100000 0.302 0.000 0.302 0.000 :0(append) 1 0.003 0.003 0.003 0.003 :0(join) 1 0.003 0.003 0.003 0.003 :0(range) 1 0.002 0.002 0.002 0.002 :0(setprofile) 1 0.002 0.002 0.697 0.697 <string>:1(<module>) 1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple) 1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000))) 0 0.000 0.000 profile:0(profiler) Проблемный участок
  • 26. ‹#› Как выглядит вывод профайлера 100006 function calls in 0.699 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 100000 0.302 0.000 0.302 0.000 :0(append) 1 0.003 0.003 0.003 0.003 :0(join) 1 0.003 0.003 0.003 0.003 :0(range) 1 0.002 0.002 0.002 0.002 :0(setprofile) 1 0.002 0.002 0.697 0.697 <string>:1(<module>) 1 0.388 0.388 0.692 0.692 example_1_profile.py:3(fizzbuzz_simple) 1 0.000 0.000 0.699 0.699 profile:0(fizzbuzz_simple(range(100000))) 0 0.000 0.000 profile:0(profiler) Артефакт
  • 28. ‹#› FizzBuzz. eval. def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 15 == 0:
 eval( 'output_array.append("FizzBuzz")', globals(), locals())
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 29. ‹#› FizzBuzz. exec. def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 15 == 0:
 (exec ‘output_array.append("FizzBuzz")')
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 30. ‹#› FizzBuzz: OOP. ArrayProcessor Replacer ItemProcessor Array of items Replacer Replacer processed as string
  • 31. ‹#› FizzBuzz: OOP. class AbstractReplacer(object):
 __metaclass__ = ABCMeta
 __slots__ = 'value', 'output'
 return_value = NotImplemented
 
 def __init__(self, value):
 pass
 
 @abstractmethod
 def validate_input(self):
 raise NotImplementedError
 
 @abstractmethod
 def check_match(self):
 raise NotImplementedError
 
 @abstractmethod
 def process(self):
 raise NotImplementedError
 
 @abstractmethod
 def get_output_value(self):
 raise NotImplementedError

  • 32. ‹#› FizzBuzz: OOP. class AbstractItemProcessor(object):
 __metaclass__ = ABCMeta
 __slots__ = 'value', 'output'
 replacer_classes = NotImplemented
 
 def __init__(self, value):
 pass
 
 @abstractmethod
 def validate_input(self):
 raise NotImplementedError
 
 @abstractmethod
 def validate_processed_value(self):
 raise NotImplementedError
 
 @abstractmethod
 def process(self):
 raise NotImplementedError
 
 @abstractmethod
 def get_replacer_classes(self):
 raise NotImplementedError
 
 @abstractmethod
 def get_output_value(self):
 raise NotImplementedError

  • 33. ‹#› FizzBuzz: OOP. class AbstractArrayProcessor(object):
 __metaclass__ = ABCMeta
 __slots__ = 'array', 'output'
 item_processer_class = NotImplemented
 
 def __init__(self, array):
 pass
 
 @abstractmethod
 def validate_input(self):
 raise NotImplementedError
 
 @abstractmethod
 def process(self):
 raise NotImplementedError
 
 @abstractmethod
 def get_item_processer_class(self):
 raise NotImplementedError
 
 @abstractmethod
 def get_output_value(self):
 raise NotImplementedError

  • 35. ‹#› FizzBuzz: OOP. class BaseReplacer(AbstractReplacer):
 return_value = None
 divider = 1
 
 def __init__(self, value):
 super(BaseReplacer, self).__init__(value)
 self.value = value
 self.validate_input()
 self.output = None
 
 def validate_input(self):
 if not isinstance(self.value, int):
 raise ImproperInputValue(self.value)
 
 def check_match(self):
 return self.value % self.divider == 0
 
 def process(self):
 if self.check_match():
 self.output = self.return_value
 
 def get_output_value(self):
 return self.output
 

  • 36. ‹#› FizzBuzz: OOP. class BaseItemProcessor(AbstractItemProcessor):
 replacer_classes = BaseReplacer,
 
 def __init__(self, value):
 super(BaseItemProcesser, self).__init__(value)
 self.value = value
 self.validate_input()
 self.output = None
 
 def validate_input(self):
 if not isinstance(self.value, int):
 raise ImproperInputValue(self.value)
 
 def validate_processed_value(self):
 if not isinstance(self.output, basestring):
 raise ImproperOutputValue
 
 def process(self):
 for replacer_class in self.get_replacer_classes():
 replacer = replacer_class(self.value)
 replacer.process()
 processed_value = replacer.get_output_value()
 if processed_value is not None:
 self.output = processed_value
 break
 
 def get_replacer_classes(self):
 return self.replacer_classes
 
 def get_output_value(self):
 return self.output

  • 37. ‹#› FizzBuzz: OOP. class BaseArrayProcessor(AbstractArrayProcessor):
 item_processor_class = BaseItemProcessor
 
 def __init__(self, array):
 super(BaseArrayProcessor, self).__init__(array)
 self.array = array
 self.validate_input()
 self.output = ''
 
 def validate_input(self):
 if not isinstance(self.array, (list, tuple, set)):
 raise ImproperInputValue(self.array)
 
 def process(self):
 output_array = []
 for item in self.array:
 item_processor_class = self.get_item_processor_class()
 item_processor = item_processor_class(item)
 item_processor.process()
 processed_item = item_processor.get_output_value()
 if processed_item:
 output_array.append(processed_item)
 self.output = ','.join(output_array)
 
 def get_item_processor_class(self):
 return self.item_processor_class
 
 def get_output_value(self):
 return self.output
 

  • 38. ‹#› FizzBuzz: OOP. FIZZ = "Fizz"
 BUZZ = "Buzz"
 FIZZBUZZ = FIZZ + BUZZ
 
 
 class MultiplesOfThreeReplacer(BaseReplacer):
 return_value = FIZZ
 divider = 3
 
 
 class MultiplesOfFiveReplacer(BaseReplacer):
 return_value = BUZZ
 divider = 5
 
 
 class MultiplesOfThreeAndFiveReplacer(BaseReplacer):
 return_value = FIZZBUZZ
 divider = 15
 
 
 class IntToStrReplacer(BaseReplacer):
 def check_match(self):
 return True
 
 def process(self):
 self.output = str(self.value)
 

  • 39. ‹#› FizzBuzz: OOP. 
 class FizzBuzzItemProcessor(BaseItemProcessor):
 replacer_classes = (
 MultiplesOfThreeAndFiveReplacer,
 MultiplesOfThreeReplacer,
 MultiplesOfFiveReplacer,
 IntToStrReplacer,
 )
 
 
 class FizzBuzzProcessor(BaseArrayProcessor):
 item_processor_class = FizzBuzzItemProcessor
 
 
 def fizzbuzz_oop(arr):
 fbp = FizzBuzzProcessor(arr)
 fbp.process()
 return fbp.get_output_value()

  • 41. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 1х FizzBuzz simple Adding eval Adding exec FizzBuzz optimized
  • 42. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple Adding eval Adding exec FizzBuzz optimized
  • 43. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval Adding exec FizzBuzz optimized
  • 44. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval 3,49037 6,34854 6,9х 3,8x Adding exec FizzBuzz optimized
  • 45. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval 3,49037 6,34854 6,9х 3,8x Adding exec 3,90273 — 6х — FizzBuzz optimized
  • 46. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval 3,49037 6,34854 6,9х 3,8x Adding exec 3,90273 — 6х — FizzBuzz optimized ? ? ? ?
  • 48. ‹#› FizzBuzz: OOP. PyCallGraph Самые ресурсоемкие вызовы
  • 50. ‹#› Оптимизация алгоритма Для данного списка натуральный чисел (int) вернуть строку со значениями через запятую, где • числа, делящиеся на 3 заменены на "Fizz"; • числа, делящиеся на 5 заменены на "Buzz"; • числа, делящиеся одновременно и на 3, и на 5 заменены на "FizzBuzz"; • остальные числа выведены как есть. Например: [1, 2, 5, 15, 3, 1, 1, 4] => "1,2,Buzz,FizzBuzz,Fizz,1,1,4" http://rosettacode.org/wiki/FizzBuzz
  • 51. ‹#› Оптимизация алгоритма def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 15 == 0:
 output_array.append("FizzBuzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array) 15?
  • 52. ‹#› Оптимизация алгоритма def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 3 == 0 and i % 5 == 0:
 output_array.append("FizzBuzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 53. ‹#› Оптимизация алгоритма def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 3 == 0 and i % 5 == 0:
 output_array.append("FizzBuzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 54. ‹#› Оптимизация алгоритма def fizzbuzz_simple(arr):
 output_array = []
 for i in arr: if i % 3 == 0:
 if i % 5 == 0:
 output_array.append("FizzBuzz")
 else:
 output_array.append("Fizz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 55. ‹#› Оптимизация алгоритма Количество сравнений для списка значений 1 .. 15 До … 39 После … 30 По времени ~ 3% разницы По количеству операций ~ 30% А что если переставить порядок сравнений?
  • 56. ‹#› Оптимизация алгоритма. Перестановка операций def fizzbuzz_simple(arr):
 output_array = []
 for i in arr:
 if i % 15 == 0:
 output_array.append("FizzBuzz")
 elif i % 5 == 0:
 output_array.append("Buzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 57. ‹#› Оптимизация алгоритма. Перестановка операций def fizzbuzz_simple(arr):
 output_array = []
 for i in arr: if i % 5 == 0:
 if i % 3 == 0:
 output_array.append("FizzBuzz")
 else:
 output_array.append("Buzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 58. ‹#› Оптимизация алгоритма. Перестановка операций Количество сравнений для списка значений 1 .. 15 Плохой вариант До … 39 После … 41 (хуже) Улучшенный вариант До … 30 После … 30 (не изменилось) От лучшего до худшего ~ 30%
  • 60. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval 3,49037 6,34854 6,9х 3,8x Adding exec 3,90273 — 6х — FizzBuzz optimized ? ? ? ?
  • 61. ‹#› Оптимизируем CPython. Lookup def fizzbuzz_simple(arr):
 output_array = []
 for i in arr: if i % 5 == 0:
 if i % 3 == 0:
 output_array.append("FizzBuzz")
 else:
 output_array.append("Buzz")
 elif i % 3 == 0:
 output_array.append("Fizz")
 else:
 output_array.append(str(i))
 return ",".join(output_array)
  • 62. ‹#› Оптимизируем CPython. Lookup def fizzbuzz_simple(arr):
 output_array = [] _append = output_array.append
 for i in arr: if i % 5 == 0:
 if i % 3 == 0:
 _append(«FizzBuzz")
 else:
 _append(«Buzz")
 elif i % 3 == 0:
 _append(«Fizz")
 else:
 _append(str(i))
 return ",".join(output_array)
  • 63. ‹#› Оптимизируем CPython. Lookup def fizzbuzz_simple(arr):
 output_array = [] _append = output_array.append
 for i in arr: if i % 5 == 0:
 if i % 3 == 0:
 _append(«FizzBuzz")
 else:
 _append(«Buzz")
 elif i % 3 == 0:
 _append(«Fizz")
 else:
 _append(str(i))
 return ",".join(output_array) 1.3x
  • 64. ‹#› FizzBuzz: Тесты CORRECT_100 = (
 "1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,"
 "16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,"
 "31,32,Fizz,34,Buzz,Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,"
 "46,47,Fizz,49,Buzz,Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,"
 "61,62,Fizz,64,Buzz,Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,"
 "76,77,Fizz,79,Buzz,Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,"
 "91,92,Fizz,94,Buzz,Fizz,97,98,Fizz,Buzz"
 ) def check_correct_100(fn):
 print 'checking function {fn.__name__}'.format(**locals()),
 output = fn(range(1, 101))
 if output == CORRECT_100:
 print '.. ok'
 else:
 print ‘.. failed'
  • 65. ‹#› Быстрый FizzBuzz def fizzbuzz_samples_helper(arr):
 for i in arr:
 if i % 3 == 0:
 if i % 5 == 0:
 yield "FizzBuzz"
 else:
 yield "Fizz"
 elif i % 5 == 0:
 yield "Buzz"
 else:
 yield False
 
 
 samples = tuple(fizzbuzz_samples_helper(xrange(15)))
  • 66. ‹#› FizzBuzz. Перестановка операций samples = (False, False, «Fizz" ,False, «Buzz", . . . , "FizzBuzz") def fizzbuzz(arr):
 output_array = [ samples[i % 15] or str(i) for i in arr]
 return ",".join(output_array)
  • 67. ‹#› FizzBuzz. Перестановка операций samples = (False, False, «Fizz" ,False, «Buzz", . . . , "FizzBuzz") def fizzbuzz(arr):
 output_array = [ samples[i % 15] or str(i) for i in arr]
 return ",".join(output_array) 1,35x
  • 68. ‹#› Быстрый FizzBuzz 
 def fizzbuzz_with_precached_samples(
 arr,
 # shorteners __join=",".join, 
 __samples=samples,
 __str=str
 ):
 return __join(__samples[i % 15] or __str(i) for i in arr)
  • 69. ‹#› Быстрый FizzBuzz 
 def fizzbuzz_with_precached_samples(
 arr,
 # shorteners __join=",".join, 
 __samples=samples,
 __str=str
 ):
 return __join(__samples[i % 15] or __str(i) for i in arr) 0,96x ?
  • 70. ‹#› FizzBuzz: Результаты cpython pypy cpython to FizzBuzz OOP cpython pypy to FizzBuzz OOP cpythonFizzBuzz OOP 24,11218 0,72933 1х 33x FizzBuzz simple 1,23326 0,23751 19,5х 101х Adding eval 3,49037 6,34854 6,9х 3,8x Adding exec 3,90273 — 6х — FizzBuzz optimized 0,72047 0,24492 33,4x 101x
  • 73. ‹#› Coroutines 64 0 LOAD_FAST 1 (__join) 3 LOAD_CLOSURE 0 (__samples) 6 LOAD_CLOSURE 1 (__str) 9 BUILD_TUPLE 2 12 LOAD_CONST 1 (<code object <genexpr> at 0x10d849930, file "./___.py", line 64>) 15 MAKE_CLOSURE 0 18 LOAD_FAST 0 (arr) 21 GET_ITER 22 CALL_FUNCTION 1 25 CALL_FUNCTION 1 28 RETURN_VALUE
  • 74. ‹#› Coroutines def fizzbuzz_co(
 # shorteners
 __join=",".join,
 __samples=samples,
 __str=str
 ):
 arr = ()
 while True:
 arr = yield __join(__samples[i % 15] or __str(i) for i in arr)
 
 _ = fizzbuzz_co()
 _.next()
 fizzbuzz_co= _.send
  • 75. ‹#› Coroutines def fizzbuzz_co(
 # shorteners
 __join=",".join,
 __samples=samples,
 __str=str
 ):
 arr = ()
 while True:
 arr = yield __join(__samples[i % 15] or __str(i) for i in arr)
 
 
 _ = fizzbuzz_co()
 _.next()
 fizzbuzz_co= _.send outputinput output = co.send(input) «инициализировать» и получить первый output создать сопроцесс заменить ссылку на метод send
  • 76. ‹#› def co(): . . . x = yield y [return None] c = co() out = c.send(Z) Coroutines Как это работает • def + yield = ключевые слова • создаем «конструктор» генератора • вызов c = co() создает генератор c • c.next() • выполнит все до первого yield, • вернет результат выражения y, • «встанет на паузу» • c.send(Z) • x = Z • продолжит выполнение до yield/return • out = y • return завершает выполнение (StopIteration)
  • 77. ‹#› Coroutines Можно обернуть в декоратор: 
 def coroutine(fn):
 _ = fn()
 _.next()
 return _.send
  • 78. ‹#› Coroutines … и поместить все внутрь (до первого yield) @coroutine
 def fizzbuzz_co():
 def fizzbuzz_samples_helper(arr):
 for i in arr:
 if i % 3 == 0:
 if i % 5 == 0:
 yield "FizzBuzz"
 else:
 yield "Fizz"
 elif i % 5 == 0:
 yield "Buzz"
 else:
 yield False
 
 __join = ",".join
 __str = str
 samples = tuple(fizzbuzz_samples_helper(xrange(15)))
 arr = ()
 
 while True:
 arr = yield __join(samples[i % 15] or __str(i) for i in arr)
  • 80. ‹#› Быстрый FizzBuzz, кэширующая функция Кэширующая функция • вычисления ресурсоемки • значения аргументов часто повторяются def cached(fn):
 cache = {}
 
 @wraps(fn)
 def decorated(arg):
 value = cache.get(arg)
 if not value:
 cache[arg] = value = fn(arg)
 return value
 
 return decorated
  • 81. ‹#› Быстрый FizzBuzz, кэширующая функция @cached
 def process_one(
 i,
 # shorteners
 __samples=samples,
 __str=str
 ):
 return __samples[i % 15] or __str(i)
 def fizzbuzz_with_cache(
 arr,
 # shorteners
 __join=",".join,
 ):
 return __join(map(process_one, arr))
  • 83. ‹#› class MakeSum(Exception):
 pass
 
 
 class ChgKoef(Exception):
 pass
 
 
 def co():
 x = None
 y = None
 k = 1
 rv = None
 
 while True:
 try:
 x, y = yield rv
 rv = k * x * y
 except MakeSum as e:
 x, y = e.args
 rv = k * (x + y)
 except ChgKoef as e:
 k = e.args[0] Coroutine based class class Cls(object):
 def __init__(self):
 self.x = None
 self.y = None
 self.k = 1
 self.rv = None
 
 def main_method(self, x, y):
 self.x = x
 self.y = y
 self.rv = self.k * self.x * self.y
 return self.rv
 
 def make_sum(self, x, y):
 self.x = x
 self.y = y
 self.rv = self.k * (self.x + self.y)
 return self.rv
 
 def chg_koef(self, k):
 self.k = k
 return self.rv
  • 84. ‹#› instance = co()
 print instance
 # <generator object co at 0x10047bbe0>
 
 print instance.next()
 # None
 
 print instance.send((1, 2))
 # 2 == 1 * 1 * 2
 
 print instance.send((3, 4))
 # 12 == 1 * 3 * 4
 
 print instance.throw(MakeSum(5, 6))
 # 11 == 1 * (5 + 6)
 
 print instance.send((7, 8))
 # 56 == 1 * 7 * 8
 
 print instance.throw(ChgKoef(10))
 # 56 (last value repeated)
 
 print instance.send((1, 2))
 # 20 == 10 * 1 * 2 Coroutine based inheritance instance = Cls()
 print instance
 # <__main__.Cls object at 0x10a1c6210>
 
 print instance.main_method(1, 2)
 # 2 == 1 * 1 * 2
 
 print instance.main_method(3, 4)
 # 12 == 1 * 3 * 4
 
 print instance.make_sum(5, 6)
 # 11 == 1 * (5 + 6)
 
 print instance.main_method(7, 8)
 # 56 == 1 * 7 * 8
 
 print instance.chg_koef(10)
 # 56 (last value repeated)
 
 print instance.main_method(1, 2)
 # 20 == 10 * 1 * 2
  • 86. ‹#› def co(
 param,
 # ___
 __some_value=5,
 __some_method=lambda: 10
 ):
 rv = None
 
 while True:
 input = yield rv
 
 def co_sub(
 param,
 # ___
 __some_value=10,
 __some_method=lambda: 20
 ):
 return co(**locals()) Coroutine based class class Cls(object):
 def __init__(self, param):
 self.param = param
 
 some_value = 5
 
 def some_method(self):
 return 10
 
 def main_method(self):
 return
 
 
 class SubCls(Cls):
 some_value = 10
 
 def some_method(self):
 return 20
  • 87. ‹#› Coroutine based class coroutine class coroutine vs class send main method 4,23 6,93 1,63x faster throw MakeSum make_sum 21,85 7,30 3x slower
  • 88. ‹#› Coroutine based class Плюсы • Основной метод работает быстрее • «Наследование» Минусы • Интерфейс «заморожен» • Основной метод «заморожен» • Код «специфичен»
  • 90. ‹#› Cython, numpy, weave, etc.. «Числодробилки» Travis Oliphant from numpy import zeros
 from scipy import weave
 
 dx = 0.1
 dy = 0.1
 dx2 = dx*dx
 dy2 = dy*dy
 
 def py_update(u):
 nx, ny = u.shape
 for i in xrange(1,nx-1):
 for j in xrange(1, ny-1):
 u[i,j] = ((u[i+1, j] + u[i-1, j]) * dy2 +
 (u[i, j+1] + u[i, j-1]) * dx2) / (2*(dx2+dy2))
 
 def calc(N, Niter=100, func=py_update, args=()):
 u = zeros([N, N])
 u[0] = 1
 for i in range(Niter):
 func(u,*args)
 return u
  • 91. ‹#› Почти тот же Python! 
 cimport numpy as np
 
 def cy_update(np.ndarray[double, ndim=2] u, double dx2, double dy2):
 cdef unsigned int i, j
 for i in xrange(1,u.shape[0]-1):
 for j in xrange(1, u.shape[1]-1):
 u[i,j] = ((u[i+1, j] + u[i-1, j]) * dy2 +
 (u[i, j+1] + u[i, j-1]) * dx2) / (2*(dx2+dy2))
 Cython, numpy, weave, etc..
  • 92. ‹#› Cython, numpy, weave, etc.. Почти «чистый С» def weave_update(u):
 code = """
 int i, j;
 for (i=1; i<Nu[0]-1; i++) {
 for (j=1; j<Nu[1]-1; j++) {
 U2(i,j) = ((U2(i+1, j) + U2(i-1, j))*dy2 + 
 (U2(i, j+1) + U2(i, j-1))*dx2) / (2*(dx2+dy2));
 }
 }
 """
 weave.inline(code, ['u', 'dx2', 'dy2'])
  • 93. ‹#› Cython, numpy, weave, etc.. Method Time (sec) relative speed (меньше-лучше) Pure python 560 250 NumPy 2,24 1 Cython 1,28 0,51 Weave 1,02 0,45 Faster Cython 0,94 0,42
  • 95. ‹#› Рецепт • найти слабое место • убедиться что все упирается в производительность кода, а не в дисковое/сетевое IO • упростить ООП до простых функций и процедур • оптимизировать алгоритм • избавиться от лишних переменных • избавиться от конструкций object.method() • использовать итераторы/генераторы вместо списков • завернуть все в сопроцессы • постоянно замерять производительность на данных, схожих с реальными • тестировать • знать когда остановиться
  • 96. ‹#› • Ссылки, литература: • Дэвид Бизли: генераторы/сопроцессы http://www.dabeaz.com/generators/ • Python и память http://www.slideshare.net/PiotrPrzymus/pprzymus-europython-2014 • Другой пример о профилировали — числа фибоначчи http://pymotw.com/2/profile/ • Про объекты, ссылки и утечки памяти http://mg.pov.lt/objgraph/ • line_profiler, memory_profiler http://www.huyng.com/posts/python- performance-analysis/ • numpy, cython, weave http://technicaldiscovery.blogspot.ru/2011/06/speeding-up- python-numpy-cython-and.html • google • Контакты: • email: iremizov@parallels.com #CodeFest • twitter: @iremizov