Python versnellen met Cython

Cython is een superset van Python waarmee je de snelheid van je code aanzienlijk kunt verbeteren. U kunt optionele typeaangiften toevoegen voor nog meer voordelen. Cython vertaalt uw code naar geoptimaliseerde C / C ++ die wordt gecompileerd naar een Python-uitbreidingsmodule. 

In deze tutorial leer je hoe je Cython installeert, krijg je onmiddellijk een directe prestatieverbetering van je Python-code en vervolgens kun je echt profiteren van Cython door typen toe te voegen en je code te profileren. Tot slot leert u meer over meer geavanceerde onderwerpen zoals integratie met C / C ++ code en NumPy die u verder kunt verkennen voor nog meer winst.

Pythagorasische triples tellen

Pythagoras was een Griekse wiskundige en filosoof. Hij is beroemd om zijn stelling van Pythagoras, waarin staat dat in een rechthoekige driehoek de som van vierkanten van de poten van de driehoeken gelijk is aan het kwadraat van de hypotenusa. Pythagorean triples zijn drie positieve gehele getallen a, b en c die zoiets a² + b² = c². Hier is een programma dat alle Pythagorean-triples vindt waarvan de leden niet groter zijn dan de opgegeven limiet.

import time def count (limit): result = 0 for a in range (1, limit + 1): voor b in bereik (a + 1, limit + 1): voor c in bereik (b + 1, limit + 1) : if c * c> a * a + b * b: pauze als c * c == (a * a + b * b): resultaat + = 1 retourresultaat als __name__ == '__main__': start = time.time () result = count (1000) duration = time.time () - start print (resultaat, duur) Output: 881 13.883624076843262 

Blijkbaar zijn er 881 triples, en het duurde iets minder dan 14 seconden om het uit te vinden. Dat is niet te lang, maar lang genoeg om vervelend te zijn. Als we meer triples tot een hogere limiet willen vinden, moeten we een manier vinden om het sneller te laten gaan. 

Het blijkt dat er aanzienlijk betere algoritmen zijn, maar vandaag concentreren we ons op het sneller maken van Python met Cython, niet op het beste algoritme voor het vinden van Pythagorean-drievoudige. 

Easy Boosting Met pyximport

De eenvoudigste manier om Cython te gebruiken is om de speciale pyximport-functie te gebruiken. Dit is een statement dat uw Cython-code direct compileert en u zonder al te veel problemen van de voordelen van native optimalisatie laat genieten. 

U moet de code in de eigen module coderen, één regel setup in uw hoofdprogramma schrijven en vervolgens zoals gebruikelijk importeren. Laten we kijken hoe het eruit ziet. Ik heb de functie verplaatst naar zijn eigen bestand met de naam pythagorean_triples.pyx. De extensie is belangrijk voor Cython. De lijn die Cython activeert is import pyximport; pyximport.install (). Vervolgens importeert het alleen de module met de telling () functie en roep het later op in de hoofdfunctie.

importeer tijd import pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - start print (result, duration) if __name__ == '__main__': main () Output: 881 9.432806253433228 

De pure Python-functie liep 50% langer. We hebben deze boost gekregen door een enkele regel toe te voegen. Helemaal niet slecht.

Bouw uw eigen uitbreidingsmodule

Hoewel pyximport erg handig is tijdens de ontwikkeling, werkt het alleen op pure Python-modules. Vaak wilt u bij het optimaliseren van de code verwijzen naar native C-bibliotheken of uitbreidingsmodules van Python. 

Om die te ondersteunen, en ook om dynamisch te compileren bij elke run, kunt u uw eigen Cython uitbreidingsmodule bouwen. U moet een klein setup.py-bestand toevoegen en onthouden om het te bouwen voordat u uw programma uitvoert, telkens wanneer u de Cython-code wijzigt. Hier is het bestand setup.py:

van distutils.core import setup van Cython.Build import cythonize setup (ext_modules = cythonize ("pythagorean_triples.pyx"))

Dan moet je het bouwen:

$ python setup.py build_ext --inplace Pythagorean_triples.pyx compileren omdat het is gewijzigd. [1/1] Cythonizing pythagorean_triples.pyx met build_ext_ext 'pythagorean_triples'-extensie maken build maken build maken / temp.macosx-10.7-x86_64-3.6 gcc -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g - fwrapv -O3 -Wall -Wstrict-prototypes -I / Gebruikers / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Users / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Gebruikers / gigi.sayfan / miniconda3 / envs / py3 / include / python3.6m -c pythagorean_triples.c -o build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o gcc -bundle -undefined dynamic_lookup -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -L / Gebruikers / gigi.sayfan / miniconda3 / envs / py3 / lib -arch x86_64 build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -o pythagorean_triples.cpython-36m-darwin.so

Zoals je kunt zien aan de output, genereerde Cython een C-bestand met de naam pythagorean_triples.c en compileert het een platform-specifiek .so-bestand, wat de uitbreidingsmodule is die Python nu kan importeren net als elke andere native uitbreidingsmodule.. 

Als je nieuwsgierig bent, neem een ​​kijkje op de gegenereerde C-code. Het is erg lang (2789 regels), stom en bevat veel extra dingen die nodig zijn om met de Python API te werken. Laten we de pyximport droppen en ons programma opnieuw uitvoeren:

import time import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - start print (result, duration) if __name__ == '__main__': main () 881 9.507064819335938 

Het resultaat is vrijwel hetzelfde als bij pyximport. Merk echter op dat ik alleen de looptijd van de gecodeerde code meet. Ik meet niet hoe lang het duurt voordat pyximport de gedecononiseerde code ter plekke compileert. In grote programma's kan dit aanzienlijk zijn.

Soorten aan uw code toevoegen

Laten we het naar het volgende niveau brengen. Cython is meer dan Python en voegt optioneel typen toe. Hier definieer ik gewoon alle variabelen als gehele getallen en de prestaties schieten omhoog:

# pythagorean_triples.pyx def count (limit): cdef int result = 0 cdef int a = 0 cdef int b = 0 cdef int c = 0 voor een binnen bereik (1, limit + 1): voor b in bereik (a + 1 , limit + 1): voor c in bereik (b + 1, limit + 1): if c * c> a * a + b * b: break if c * c == (a * a + b * b): resultaat + = 1 retourresultaat ---------- # main.py importeer tijd importeer pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - start print (result, duration) if __name__ == '__main__': main () Output: 881 0.056414127349853516 

Ja. Dat is correct. Door een aantal gehele getallen te definiëren, loopt het programma in minder dan 57 milliseconden, vergeleken met meer dan 13 seconden met pure Python. Dat is bijna een 250X verbetering.

Profilering van uw code

Ik gebruikte de tijdmodule van Python, die de wandtijd meet en meestal behoorlijk goed is. Als u een preciezere timing van kleine codefragmenten wilt, overweeg dan om de timeit-module te gebruiken. Hier leest u hoe u de uitvoering van de code kunt meten met timeit:

>>> import timeit >>> timeit.timeit ('count (1000)', setup = "from import count count pythagorean_triples", nummer = 1) 0.05357028398429975 # 10 keer uitgevoerd >>> timeit.timeit ('count (1000)' , setup = "from import count count pythagorean_triples", getal = 10) 0.5446877249924 

De timeit () functie neemt een instructie om uit te voeren, een setup-code die niet wordt gemeten en het aantal keren dat de gemeten code moet worden uitgevoerd.

Geavanceerde onderwerpen

Ik heb hier net de oppervlakte geschraapt. Je kunt veel meer met Cython doen. Hier zijn een paar onderwerpen die de prestaties van uw code verder kunnen verbeteren of waarmee Cython kan worden geïntegreerd met andere omgevingen:

  • C-code bellen
  • interactie met de Python C API en de GIL
  • met behulp van C ++ in Python
  • Cython-code naar PyPY porteren
  • parallellisme gebruiken
  • Cython en NumPy
  • het delen van aangiften tussen Cython-modules

Conclusie

Cython kan met een kleine inspanning twee orden van grootte prestatieverbetering produceren. Als je niet-triviale software ontwikkelt in Python, is Cython een no-brainer. Het heeft heel weinig overhead en je kunt het geleidelijk aan je codebase introduceren.

Aarzel niet om te zien wat we te koop aanbieden en om te studeren op de markt, en aarzel niet om vragen te stellen en uw waardevolle feedback te geven met behulp van de onderstaande feed.