88
Ремизов Иван Cloud Architect Оптимизация производительности Python

Ремизов Иван Cloud Architect Оптимизация производительности Python

Embed Size (px)

Citation preview

Page 1: Ремизов Иван Cloud Architect Оптимизация производительности Python

Ремизов ИванCloud Architect

Оптимизация производительности Python

Page 2: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Картинка для привлечения внимания

* CPython, без привлечения внешних зависимостей, компиляции и тп

30x

Page 3: Ремизов Иван Cloud Architect Оптимизация производительности Python
Page 4: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Python

Рассматриваем язык:

Python 2.x

Конкретные реализации:

CPython (*nix default)

PyPy (JIT)

Page 5: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Проблемы приложения.

Какие проблемы вообще бывают?

• неудачные архитектурные решения

• неудачно выбранные компоненты и фреймворки

• медленный I/O

• высокий расход памяти, утечки памяти

• медленный код

Page 6: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Проблемы приложения.

Как решается большинство проблем?

• добавление воркеров

• кеширование

• отложенные задания, очереди

• замена компонентов

• map/reduce

• изменение архитектуры

• …

Page 7: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Когда это критично и не решаемо «привычными» способами?

Обработка потоковых данных

пример: процессинг датчиков (акселерометры, гироскопы)

Десериализация

пример: JSON, pickle, ..

Авторегрессия

пример: EMA (скользящая средняя), численное интегрирование, ряды

Стейт-машины

пример: AI, синтаксические анализаторы текста

Медленный код.

Page 8: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Профилирование специальными утилитами• ручной профайлинг (тайминг)• статистический профайлинг (сэмплинг)• событийный профайлинг (граф вызовов)

Логгирование и сбор статистики• настройка конфигов apache/nginx/…• логи приложения

Как найти критические участки кода?

Page 9: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Утилиты

• profile/cprofile

• pycallgraph

• dis (иногда бывает полезно)

Выбор огромен

• line_profiler

• hotshot

• gprof2dot

• memory_profiler

• objgraph

• memprof

• для django есть миддлвары с картинками и графиками• django debug toolbar• django live profiler

• …

Profiling.

Page 10: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Задача: профилирование живого WEB-сервера

• мы не хотим чтобы профилировщик значительно снижал производительность

• мы хотим получить более-менее репрезентативные данные

Решение:

1.поднять апстрим на ~1% и собирать статистику с него (*)

2.воспроизвести на стейджинге/тестовом окружении

Альтернатива:

• настраиваем access logs

• смотрим, где медленно

• разбираемся почему

Итого.

Page 11: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

• проводить серию испытаний и замерять среднее время

• по возможности необходимо снизить влияние наведенных эффектов:• сборщик мусора (если мы не хотим его учитывать), • I/O блокировки,• профилировщик и тп

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

• имея дело с JIT, всегда сначала проводить «разогревочные итерации» (* PyPy на JIT компилляцию нужно не менее 0.2c — см. доки)

• тест не должен молотить впустую, иначе JIT может его "вырезать"

• разогревочный пробег и целевой• не должны значительно различаться• код, оптимизированный JIT-ом, должен работать быстрее, если нет,

надо разбираться что не так

Как правильно писать тесты на производительность?

Page 12: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

• Регрессионные тесты должны быть

• Не нужно делать гипотез и предположений о ботлнэке. Замерять и профайлить!

• Проблема в I/O или нет?

• Первое что стоит оптимизировать — алгоритм• снижать сложность, упрощать логику• уменьшать количество ветвлений• увеличивать избератильность• уменьшать размеры циклов

• Проблема скорее всего в каком-то из циклов

• Все что не меняется, не нужно пересчитывать много раз• регулярки, конфигурации

• eval, exec — плохо

• Не увлекаться!

Что всегда надо держать в голове

Page 13: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

CPython — интерпретатор.

Он честно интерпретирует каждую строку кода.

• Lookup-ы — очень дороги • локальные/глобальные переменные• замыкание• атрибуты и методы

• Запоминание переменных дорого

• Создание объектов — дорого

• Изменение размеров объектов в памяти — дорого

Особенности присущие CPython

Page 14: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 15: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

ПРИМЕРЫ

Page 16: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 17: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 18: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 19: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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'

Page 20: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 21: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Инструменты

• Юнит-тесты или иной способ проверки правильности алгоритма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))

Page 22: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Как выглядит вывод 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

. . .

Page 23: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Как выглядит вывод профайлера

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)

Page 24: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Как выглядит вывод профайлера

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)

Проблемныйучасток

Page 25: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Как выглядит вывод профайлера

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)

Артефакт

Page 26: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Как выглядит вывод PyCallGraph

Page 27: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 28: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 29: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz: OOP.

ArrayProcessorArrayProcessor

ReplacerReplacer

ItemProcessorItemProcessor

Array of items

ReplacerReplacerReplacerReplacer

processed as string

Page 30: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 31: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 32: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 33: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz: OOP.

class ImproperInputValue(Exception): pass

class ImproperOutputValue(Exception): pass

Page 34: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 35: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 36: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 37: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 38: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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()

Page 39: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

ЗАМЕРЫ

Page 40: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz: Результаты

cpython

pypy cpython

to FizzBuzz OOP cpython

pypy

to FizzBuzz OOP cpython

FizzBuzz OOP

24,11218

FizzBuzz simple

Adding eval

Adding exec

FizzBuzz optimized

Page 41: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 42: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 43: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 44: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 45: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

? ? ? ?

Page 46: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz: OOP. PyCallGraph

Page 47: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

О ПРЕЖДЕВРЕМЕННОЙ ОПТИМИЗАЦИИ

Page 48: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

Для данного списка натуральный чисел (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

Page 49: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

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?

Page 50: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

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)

Page 51: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

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)

Page 52: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

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)

Page 53: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма

Количество сравнений для списка значений 1 .. 15

До … 39

После … 30

По времени ~ 3 % разницы

А что если переставить порядок сравнений?

Page 54: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма. Перестановка операций

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)

Page 55: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма. Перестановка операций

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)

Page 56: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизация алгоритма. Перестановка операций

Количество сравнений для списка значений 1 .. 15

Плохой вариант

До … 39

После … 41 (хуже)

Улучшенный вариант

До … 30

После … 30 (не изменилось)

От лучшего до худшего ~ 30%

Page 57: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

ОПТИМИЗИРУЕМ CPYTHON

Page 58: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

? ? ? ?

Page 59: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизируем 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)

Page 60: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизируем 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)

Page 61: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Оптимизируем 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

Page 62: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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'

Page 63: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый 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)))

Page 64: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz. Перестановка операций

def fizzbuzz(arr): output_array = [ samples[i % 15] or str(i) for i in arr] return ",".join(output_array)

Page 65: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

FizzBuzz. Перестановка операций

def fizzbuzz(arr): output_array = [ samples[i % 15] or str(i) for i in arr] return ",".join(output_array)

1,35x

Page 66: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый 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)

Page 67: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый 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

Page 68: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 69: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

СОПРОЦЕСС / COROUTINE

Page 70: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 71: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 72: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 73: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 74: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Coroutines

def coroutine(fn): _ = fn() _.next() return _.send

Page 75: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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)

Page 76: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

КЕШИРУЮЩИЕ ФУНКЦИИ

Page 77: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый 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

Page 78: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый FizzBuzz, кэширующая функция

@cacheddef process_one( i, # shorteners __samples=samples, __str=str): return __samples[i % 15] or __str(i)

Page 79: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Быстрый FizzBuzz, кэширующая функция

def fizzbuzz_with_cache( arr, # shorteners __join=",".join,): return __join(map(process_one, arr))

Page 80: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

«ЧИСЛОДРОБИЛКИ»

Page 81: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 82: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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))

Page 83: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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'])

Page 84: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

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

Page 85: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

РЕЦЕПТ

Page 86: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Рецепт

• найти слабое место

• убедиться что все упирается в производительность кода, а не в дисковое/сетевое IO

• упростить ООП до простых функций и процедур

• оптимизировать алгоритм

• избавиться от лишних переменных

• избавиться от конструкций object.method()

• использовать итераторы/генераторы вместо списков

• завернуть все в сопроцессы

• постоянно замерять производительность на данных, схожих с реальными

• тестировать

• знать когда остановиться

Page 87: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

• Ссылки, литература:

• Дэвид Бизли: генераторы/сопроцессы 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: [email protected] #piterPy

• twitter: @iremizov

Page 88: Ремизов Иван Cloud Architect Оптимизация производительности Python

‹#›

Q&A