Многоядрен Python: Трудна, достойна и достижима цел

За всички страхотни и удобни функции на Python една цел остава недостъпна: приложенията на Python, работещи на референтния интерпретатор CPython и използващи паралелно множество ядра на процесора.

Това отдавна е един от най-големите препъни камъни на Python, особено след като всички заобикалящи решения са тромави. Неотложността за намиране на дългосрочно решение на проблема нараства, по-специално, тъй като разчитането на ядрото на процесорите продължава да се увеличава (вж. 24-ядреният гигант на Intel).

Една ключалка за всички

В действителност е възможно да се използват нишки в приложенията на Python - много от тях вече го правят. Това, което  не е възможно, е CPython да изпълнява многонишкови приложения, като всяка нишка се изпълнява паралелно на различно ядро. Вътрешното управление на паметта на CPython не е безопасно за нишки, така че интерпретаторът изпълнява само по една нишка наведнъж, превключвайки между тях при необходимост и контролирайки достъпа до глобалното състояние.

Този заключващ механизъм, Global Interpreter Lock (GIL), е най-голямата причина CPython да не може да изпълнява паралелно паралелно. Има някои смекчаващи фактори; например, I / O операции като четене на диск или мрежа не са обвързани с GIL, така че те могат да работят свободно в собствените си нишки. Но всичко, което е многонишко и свързано с процесора, е проблем.

За програмистите на Python това означава, че тежките изчислителни задачи, които се възползват от разпространението в множество ядра, не работят добре, забранявайки използването на външна библиотека. Удобството при работа в Python идва с големи разходи за производителност, които стават все по-трудни за преглъщане, тъй като на преден план излизат също толкова удобни езици като Google Go.

Изберете ключалката

С течение на времето се появиха множество възможности, които подобряват - но не премахват - границите на GIL. Една стандартна тактика е да стартирате множество екземпляри на CPython и да споделяте контекст и състояние между тях; всеки екземпляр се изпълнява независимо от другия в отделен процес. Но както обяснява Джеф Кнуп, печалбите, осигурени от паралелно изпълнение, могат да бъдат загубени от усилията, необходими за споделяне на състоянието, така че тази техника е най-подходяща за продължителни операции, които обединяват резултатите си с течение на времето.

C разширенията не са обвързани с GIL, така че много библиотеки за Python, които се нуждаят от скорост (като библиотеката математика и статистика Numpy), могат да работят в множество ядра. Но ограниченията в самия CPython остават. Ако най-добрият начин да избегнете GIL е да използвате C, това ще отблъсне повече програмисти от Python и към C.

PyPy, версията на Python, която компилира код чрез JIT, не се отървава от GIL, но го компенсира, като просто изпълнява кода по-бързо. В някои отношения това не е лош заместител: Ако скоростта е основната причина да наблюдавате многопоточност, PyPy може да е в състояние да осигури скоростта без усложненията при многопоточност.

И накрая, самият GIL беше преработен донякъде в Python 3, с по-добър манипулатор за превключване на нишки. Но всички основни предположения - и ограничения - остават. Все още има GIL и все още продължава производството.

Няма GIL? Няма проблем

Въпреки всичко това, търсенето на без GIL Python, съвместим със съществуващите приложения, продължава. Други внедрения на Python са премахнали изцяло GIL, но на цена. Jython, например, работи върху JVM и използва системата за проследяване на обекти на JVM вместо GIL. IronPython възприема същия подход чрез CLR на Microsoft. Но и двамата страдат от непостоянна производителност и понякога работят много по-бавно от CPython. Те също така не могат лесно да взаимодействат с външен C код, така че много съществуващи приложения на Python няма да работят.

PyParallel, проект, създаден от Трент Нелсън от Continuum Analytics, е „експериментална, доказателна за концепция вилица на Python 3, предназначена за оптимално използване на множество ядра на процесора“. Той не премахва GIL, но подобрява въздействието му, като замества asyncмодула, така че приложенията, които използват  asyncза паралелизъм (като многонишкови I / O като уеб сървър) се възползват най-много. Проектът е неактивен от няколко месеца, но в документацията му се посочва, че разработчиците му са удобни да отделят време, за да го оправят, така че в крайна сметка той може да бъде включен в CPython: „Няма нищо лошо в бавното и стабилно, стига да се насочите в правилната посока. "

Един дългогодишен проект на създателите на PyPy е версия на Python, която използва техника, наречена „софтуерна транзакционна памет“ (PyPy-STM). Предимството, според създателите на PyPy, е „можете да правите незначителни ощипвания на съществуващите си програми с много нишки и да ги накарате да използват множество ядра“.

PyPy-STM звучи като магия, но има два недостатъка. Първо, това е незавършена работа, която понастоящем поддържа само Python 2.x, и второ, тя все още отнема производителност за приложения, работещи на едно ядро. Тъй като една от разпоредбите, цитирани от създателя на Python Guido van Rossum за всякакви опити за премахване на GIL от CPython, е, че заместването му не трябва да влошава производителността на едноядрени еднопоточни приложения, подобна корекция няма да попадне в CPython в сегашното си състояние.

Побързайте и изчакайте

Лари Хейстингс, основен разработчик на Python, сподели някои от своите виждания на PyCon 2016 за това как GIL може да бъде премахнат. Хейстингс документира опитите си да премахне GIL и по този начин завърши с версия на Python, която няма GIL, но работеше мъчително бавно поради постоянни пропуски в кеша.

Можете да загубите GIL, обобщи Хейстингс, но трябва да имате някакъв начин да гарантирате, че само една нишка в даден момент променя глобални обекти - например, като специална нишка в интерпретатора се справя с такива промени в състоянието.

Дългосрочна добра новина е, че ако и когато CPython хвърли GIL, разработчиците, използващи езика, вече ще бъдат готови да използват многопоточност. Много промени, включени в синтаксиса на Python, като опашки и async/ awaitключови думи за Python 3.5, улесняват разпределянето на задачите между ядра на високо ниво.

И все пак, количеството работа, необходима, за да се направи Python GIL-малко, но гарантира, че ще се покаже първо в отделно изпълнение като PyPy-STM. Тези, които искат да изпробват система без GIL, могат да го направят чрез подобно усилие на трета страна, но оригиналният CPython засега вероятно ще остане недокоснат. Тук се надяваме, че чакането не е много по-дълго.