Введение в gil и новый gil

33
Введение в потоки GIL и новый GIL Андрей Нехайчик Wargaming.net

Upload: python-meetup

Post on 16-Jun-2015

470 views

Category:

Technology


11 download

DESCRIPTION

Автор: Андрей Нехайчик (Wargaming.net | COOO «Гейм Стрим») — Треды, отличия от потоков. — Как использовать треды. — Тестирование производительности (и облом). — Представление GIL, как он работает. — Освобождение по I/O, 100 тиков. — Зачем нужен GIL. — Проблемы переключения потоков (медленный захват). — Проблема 100 тиков. — Проблема отсутствия приоритетов и их типов. — Новый GIL, 5 миллисекунд, drop_request. — Когда drop_request не работает. — Соревнование CPU и I/O тредов. — Как борются с GIL: тезисы о numpy, Jython, multiprocessing.

TRANSCRIPT

Page 1: Введение в GIL и новый GIL

Введение в потокиGIL и новый GIL

Андрей НехайчикWargaming.net

Page 2: Введение в GIL и новый GIL

Из чего сделана эта презентация

3 доклада David Beazley:● Inside the Python GIL, июнь 2009● Inside the New GIL, январь 2010● Understanding the Python GIL,

февраль 2010

- Специфический внутряк для разработчиков ядра питона

+ Свежие тесты

http://www.dabeaz.com/GIL/

Page 3: Введение в GIL и новый GIL

Немного о потоках

● для распараллеливания однотипных задач

● используется pthread

Отличия от процессов● поток – наименьшая единица обработки● поток – составной элемент процесса● потоки работают в едином адресном

пространстве● потоки “дешевле” и, обычно, ОС проще

ими манипулировать

Page 4: Введение в GIL и новый GIL

Как работают потоки в идеале1 ядро

Page 5: Введение в GIL и новый GIL

Как работают потоки в идеале3 ядра

Page 6: Введение в GIL и новый GIL

Пример

def count(n): while n > 0: n -= 1

t1 = Thread(target=count, args=(100000000, ))t2 = Thread(target=count, args=(100000000, ))

t1.start()t2.start()t1.join()t2.join()

Page 7: Введение в GIL и новый GIL

Пример

def count(n): while n > 0: n -= 1

t1 = Thread(target=count, args=(100000000, ))t2 = Thread(target=count, args=(100000000, ))

t1.start()t2.start()t1.join()t2.join()

прекратите тиражировать

бесполезный пример

Page 8: Введение в GIL и новый GIL

Пример: поиск простых чиселdef is_odd_prime(num): for multiplier in range(3, num, 2): if num % multiplier == 0: return False return True

def store_odd_primes(storage, max_number, start_number=3): for num in range(start_number, max_number + 1, 2): if is_odd_prime(num): storage.append(num)

numbers = [2]store_odd_primes(numbers, 20000)

store_odd_primes(numbers, 40000, start_number=20001)

print len(numbers)

Page 9: Введение в GIL и новый GIL

Пример: поиск простых чиселfrom threading import Thread

...

numbers = [2]

thread1=Thread(target=store_odd_primes, args=(numbers, 20000, ))

thread1.start()

thread2=Thread(target=store_odd_primes, args=(numbers, 40000, ), kwargs={'start_number': 20001})

thread2.start()

thread1.join()

thread2.join()

print len(numbers)

Page 10: Введение в GIL и новый GIL

Результаты

Поиск простых чисел до 40000 неоптимальным алгоритмом

Окружение Последовательноевыполнение

2 потока(1 ядро)

2 потока(2 ядра)

python 2.7.5(2GHz, Gentoo)

5.40 5.41 5.92 (+10%)

python 3.2.5 8.80 9.02 12.11 (+40%)

python 3.3.3 8.90 8.90 12.20 (+40%)

python 2.7.3(1GHz, iOS 7, iPad 3)

23.90 - 25.30 (+5%)

python 2.7.2(2.5GHz, OS X 10.8)

3.56 - 3.90 (+10%)

Page 11: Введение в GIL и новый GIL

Потоки в Python

● Существует GIL и гарантирует последовательное выполнение байткода

● Каждые 100 тиков: освобождение и захват GIL

● 1 тик – одна или более инструкций байткода

● IO освобождает GIL● Си-модули могут освобождать GIL● Освобождение и захват GIL –

дополнительные накладные расходы

Page 12: Введение в GIL и новый GIL

Потоки в Python

Page 13: Введение в GIL и новый GIL

Обработка сигналов

Page 14: Введение в GIL и новый GIL

Профилирование однопоточной программы

python -m cProfile prime_seq.py # 10000

ncalls tottime filename:lineno(function) 1 0.001 prime_seq.py:1(<module>)

1 0.015 prime_seq.py:3

(get_prime_list)

9999 0.797 prime_seq.py:5(is_prime)1229 0.001 {method 'append' of 'list' objects}

1 0.000 {method 'disable' of

'_lsprof.Profiler' objects}

Page 15: Введение в GIL и новый GIL

Профилирование многопоточной программыncalls tottime percall cumtime percall filename:lineno(function)

………

2 0.000 0.000 0.886 0.443 threading.py:909(join)

………

25 0.886 0.035 0.886 0.035 {method 'acquire' of 'thread.lock'}

97 0.000 0.000 0.000 0.000 {method 'append' of 'list'}

1 0.000 0.000 0.000 0.000 {method 'disable' of

'_lsprof.Profiler'}

2 0.000 0.000 0.000 0.000 {method 'extend' of 'list'}

2 0.000 0.000 0.000 0.000 {method 'get' of 'dict'}

1 0.000 0.000 0.000 0.000 {method 'insert' of 'list'}

2 0.000 0.000 0.000 0.000 {method 'items' of 'dict'}

1 0.000 0.000 0.000 0.000 {method 'lower' of 'str'}

12 0.000 0.000 0.000 0.000 {method 'release' of 'thread.lock'}

1 0.000 0.000 0.000 0.000 {method 'rfind' of 'str'}

2 0.000 0.000 0.000 0.000 {method 'setter' of 'property'}

6 0.000 0.000 0.000 0.000 {method 'write' of 'file'}

Page 16: Введение в GIL и новый GIL

Вернёмся к начальному примеру

def count(n): while n > 0: n -= 1

Последовательное 2 потока(2 ядра)

Разница

python 2.7.5,Gentoo, 2Ghz

20.0 27.7 x1.4

python 3.2.5 19.7 34.5 x1.8

David BeazleyOS X, 2GHz

24.6 45.5 x1.8

python 2.7.2OS X 10.8, 2.5 Ghz i5

15.2 23.7 x1.6

Page 17: Введение в GIL и новый GIL

Почему такие большие накладные расходы (1 ядро)?

Page 18: Введение в GIL и новый GIL

Почему такие большие накладные расходы (2 ядра)?

Page 19: Введение в GIL и новый GIL

Визуализация попыток захвата

Page 20: Введение в GIL и новый GIL

Промежуточные итоги

● Код параллельно не выполняется● Но IO (всегда) и CPython расширения

(некоторые) освобождают GIL● Нет планировщика потоков● Сигналы обрабатываются в главном

потоке● Потоки с интенсивным использованием

CPU передерживают GIL● Провальные попытки захвата GIL

Page 21: Введение в GIL и новый GIL

Зачем нужен GIL?

● Защита операций работы с памятью в ядре

● Упрощение кода● Скорость выше, если код проще

Page 22: Введение в GIL и новый GIL

GIL в python 3.2

● Первое серьёзное изменение со времён 1992 года

● Вместо счётчика тиков - gil_drop_request● Также добавлен таймаут в 5мс

Page 23: Введение в GIL и новый GIL

Как это работает?

Page 24: Введение в GIL и новый GIL

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

Page 25: Введение в GIL и новый GIL

Проблемы из-за отсутствия планировщика

Page 26: Введение в GIL и новый GIL

Замедление IO

Page 27: Введение в GIL и новый GIL

Какие планы по GIL

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

программ● не изменит скорость для однопоточных● будет совместим с текущим API ядра● оставит такое же поведение GC

Page 28: Введение в GIL и новый GIL

Как обойти GIL

● Для IO этого делать не надо● Использовать специализированные

библиотеки:https://wiki.python.org/moin/ParallelProcessing (например scipy)

● Использовать другие интерпретаторы● Использовать multiprocessing

Page 29: Введение в GIL и новый GIL

Использовать другие интерпретаторы

Последовательное 2 потока(2 ядра)

4 потока(4 ядра)

jython 2.5.3(Gentoo, i3 3Ghz)

10.70 8.65 (x1.24) 6.85 (x1.56)

pypy 2.0.2(Gentoo, i3 3Ghz)

2.75 2.85 2.90

Page 30: Введение в GIL и новый GIL

Multiprocessingfrom multiprocessing import Process, Pipe

def store_odd_primes(pipe_conn, max_number, start_number=3):

storage = []

for num in range(start_number, max_number + 1, 2):

if is_odd_prime(num):

storage.append(num)

pipe_conn.send(storage)

numbers = [2]

parent_conn, child_conn = Pipe()

proc1 = Process(target=store_odd_primes, args=(child_conn, 20000, ))

proc2 = Process(target=store_odd_primes, args=(child_conn, 40000, ), kwargs={'start_number': 20001})

proc1.start();proc2.start();proc1.join();proc2.join()

numbers += parent_conn.recv()

numbers += parent_conn.recv()

print len(numbers)

Page 31: Введение в GIL и новый GIL

Multiprocessing

Последовательное 2 процесса(2 ядра)

4 потока(4 ядра)

python 2.6.8(Gentoo, i3 3Ghz)

3.80 2.80 (x1.35) 2.08 (x1.83)

python 3.2.5(Gentoo, i3 3Ghz)

5.05 3.68 (x1.37) 3.10 (x1.63)

pypy 2.0.2(Gentoo, i3 3Ghz)

0.50 0.42 (x1.20) 0.33 (x1.55)

Page 32: Введение в GIL и новый GIL

Выводы

● Параллельной многопоточности в Python по умолчанию нет

● Она частично существует для специализированных расширений и операций IO

● GIL почти никогда не мешает● А когда мешает мы знаем как с этим

бороться

Page 33: Введение в GIL и новый GIL

Спасибо.