Ремизов Иван Cloud Architect Оптимизация производительности...
-
Upload
katrina-chandler -
Category
Documents
-
view
228 -
download
0
Transcript of Ремизов Иван Cloud Architect Оптимизация производительности...
Ремизов ИванCloud Architect
Оптимизация производительности Python
‹#›
Картинка для привлечения внимания
* CPython, без привлечения внешних зависимостей, компиляции и тп
30x
‹#›
Python
Рассматриваем язык:
Python 2.x
Конкретные реализации:
CPython (*nix default)
PyPy (JIT)
‹#›
Проблемы приложения.
Какие проблемы вообще бывают?
• неудачные архитектурные решения
• неудачно выбранные компоненты и фреймворки
• медленный I/O
• высокий расход памяти, утечки памяти
• медленный код
‹#›
Проблемы приложения.
Как решается большинство проблем?
• добавление воркеров
• кеширование
• отложенные задания, очереди
• замена компонентов
• map/reduce
• изменение архитектуры
• …
‹#›
Когда это критично и не решаемо «привычными» способами?
Обработка потоковых данных
пример: процессинг датчиков (акселерометры, гироскопы)
Десериализация
пример: JSON, pickle, ..
Авторегрессия
пример: EMA (скользящая средняя), численное интегрирование, ряды
Стейт-машины
пример: AI, синтаксические анализаторы текста
Медленный код.
‹#›
Профилирование специальными утилитами• ручной профайлинг (тайминг)• статистический профайлинг (сэмплинг)• событийный профайлинг (граф вызовов)
Логгирование и сбор статистики• настройка конфигов apache/nginx/…• логи приложения
Как найти критические участки кода?
‹#›
Утилиты
• profile/cprofile
• pycallgraph
• dis (иногда бывает полезно)
Выбор огромен
• 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 на JIT компилляцию нужно не менее 0.2c — см. доки)
• тест не должен молотить впустую, иначе JIT может его "вырезать"
• разогревочный пробег и целевой• не должны значительно различаться• код, оптимизированный JIT-ом, должен работать быстрее, если нет,
надо разбираться что не так
Как правильно писать тесты на производительность?
‹#›
• Регрессионные тесты должны быть
• Не нужно делать гипотез и предположений о ботлнэке. Замерять и профайлить!
• Проблема в I/O или нет?
• Первое что стоит оптимизировать — алгоритм• снижать сложность, упрощать логику• уменьшать количество ветвлений• увеличивать избератильность• уменьшать размеры циклов
• Проблема скорее всего в каком-то из циклов
• Все что не меняется, не нужно пересчитывать много раз• регулярки, конфигурации
• eval, exec — плохо
• Не увлекаться!
Что всегда надо держать в голове
‹#›
CPython — интерпретатор.
Он честно интерпретирует каждую строку кода.
• Lookup-ы — очень дороги • локальные/глобальные переменные• замыкание• атрибуты и методы
• Запоминание переменных дорого
• Создание объектов — дорого
• Изменение размеров объектов в памяти — дорого
Особенности присущие CPython
‹#›
PyPy использует JIT.
PyPy пытается исполнить то, что вы имели в виду
Исполняется совсем не тот код, который вы пишите.
• JIT scope != trace: locals(), globals(), sys._getframe(), sys.exc_info(), sys.settrace, …— сильно замедляют PyPy
• На JIT компиляцию требуется время (>0.2s)• то, что «гоняется редко» — оптимизировано не будет
• eval, exec — сильно замедляют PyPy
• Модули написанные на C не оптимизируются и рекомендуется использовать их Python-версию.
Особенности присущие 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 gcimport hashlibimport timefrom 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)
• Модуль disfrom dis import dis
dis(fizzbuzz_simple)
• Модуль Profilefrom profile import run
run('fizzbuzz_simple(range(100000))')
• Утилита Pycallgraphfrom 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.
ArrayProcessorArrayProcessor
ReplacerReplacer
ItemProcessorItemProcessor
Array of items
ReplacerReplacerReplacerReplacer
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 cpython
FizzBuzz OOP
24,11218
1х
FizzBuzz simple
Adding eval
Adding exec
FizzBuzz optimized
‹#›
FizzBuzz: Результаты
cpython
pypy cpython
to FizzBuzz OOP cpython
pypy
to FizzBuzz OOP cpython
FizzBuzz 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 cpython
FizzBuzz 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 cpython
FizzBuzz 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 cpython
FizzBuzz 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 cpython
FizzBuzz 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
‹#›
О ПРЕЖДЕВРЕМЕННОЙ ОПТИМИЗАЦИИ
‹#›
Оптимизация алгоритма
Для данного списка натуральный чисел (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 % разницы
А что если переставить порядок сравнений?
‹#›
Оптимизация алгоритма. Перестановка операций
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 cpython
FizzBuzz 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. Перестановка операций
def fizzbuzz(arr): output_array = [ samples[i % 15] or str(i) for i in arr] return ",".join(output_array)
‹#›
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 cpython
FizzBuzz 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()
c.send(X)
Как это работает
•def + yield = ключевые слова
•объявление с ключевыми словами создает «конструктор» генератора
•вызов c = co() создает генератор c
•c.next() выполняет все до первого yield, вернет результат выражения y, «встанет на паузу»
•c.send(X) продолжит выполнение, присвоит значение X переменной x, продолжит выполнение до следующего yield/return
•return завершает выполнение (исключение StopIteration)
Coroutines
‹#›
Coroutines
def coroutine(fn): _ = fn() _.next() return _.send
‹#›
Coroutines
@coroutinedef 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, кэширующая функция
@cacheddef process_one( i, # shorteners __samples=samples, __str=str): return __samples[i % 15] or __str(i)
‹#›
Быстрый FizzBuzz, кэширующая функция
def fizzbuzz_with_cache( arr, # shorteners __join=",".join,): return __join(map(process_one, arr))
‹#›
«ЧИСЛОДРОБИЛКИ»
‹#›
Cython, numpy, weave, etc..
«Числодробилки»
Travis Oliphant
from numpy import zerosfrom scipy import weave
dx = 0.1dy = 0.1dx2 = dx*dxdy2 = 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
‹#›
Cython, numpy, weave, etc..
Почти тот же 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..
Почти «чистый С»
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
• Контакты:
• email: [email protected] #piterPy
• twitter: @iremizov
‹#›
Q&A