Fremskynde Python Med Cython

Cython er en superset av Python som lar deg forbedre kodenes hastighet betydelig. Du kan legge til valgfrie typeerklæringer for enda større fordeler. Cython oversetter koden til optimalisert C / C + + som blir kompilert til en Python forlengelsesmodul. 

I denne opplæringen lærer du hvordan du installerer Cython, få en umiddelbar ytelseforbedring av Python-koden gratis, og hvordan kan du virkelig dra nytte av Cython ved å legge til typer og profilere koden din. Til slutt lærer du om mer avanserte emner som integrering med C / C + + kode og NumPy som du kan utforske videre for enda større gevinster.

Å telle pythagoriske tripper

Pythagoras var en gresk matematiker og filosof. Han er kjent for sin pythagoriske teorem, som sier at i en rettvinklet trekant er summen av kvadrater av trekantens bein lik plassen i hypotenusen. Pythagoranske tripler er noen tre positive heltall a, b og c som slik a² + b² = c². Her er et program som finner alle de pythagoreanske tripplene hvis medlemmer ikke er større enn den angitte grensen.

importtid def telle (grense): resultat = 0 for et intervall (1, grense + 1): for b i rekkevidde (a + 1, grense + 1): for c i rekkevidde (b + 1, grense + 1) : hvis c * c> a * a + b * b: pause hvis c * c == (a * a + b * b): resultat + = 1 returresultat hvis __name__ == '__main__': start = time.time () resultat = telle (1000) varighet = time.time () - start utskrift (resultat, varighet) Utgang: 881 13.883624076843262 

Tilsynelatende er det 881 tripler, og det tok programmet litt mindre enn 14 sekunder for å finne det ut. Det er ikke for lenge, men lenge nok til å være irriterende. Hvis vi vil finne flere trippler opp til en høyere grense, bør vi finne en måte å få det til å gå raskere. 

Det viser seg at det er vesentlig bedre algoritmer, men i dag fokuserer vi på å gjøre Python raskere med Cython, ikke på den beste algoritmen for å finne Pythagorean tripler. 

Enkel oppmuntring Med pyximport

Den enkleste måten å bruke Cython på er å bruke den spesielle pyximport-funksjonen. Dette er en uttalelse som samler Cython-koden i fly, og lar deg nyte fordelene ved innfødt optimalisering uten for mye trøbbel. 

Du må sette koden til cythonize i sin egen modul, skrive en linje med oppsettet i hovedprogrammet, og deretter importere det som vanlig. La oss se hvordan det ser ut. Jeg flyttet funksjonen til egen fil kalt pythagorean_triples.pyx. Utvidelsen er viktig for Cython. Linjen som aktiverer Cython er importere pyximport; pyximport.install (). Deretter importerer den bare modulen med telle() funksjon og senere påkaller den i hovedfunksjonen.

importtid import pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - start utskrift (resultat, varighet) hvis __name__ == '__main__': hoved () Utgang: 881 9.432806253433228 

Den rene Python-funksjonen løp 50% lenger. Vi fikk dette løftet ved å legge til en enkelt linje. Ikke verst.

Bygg din egen utvidelsesmodul

Mens pyximport er veldig praktisk under utvikling, fungerer det bare på rene Python-moduler. Ofte når du optimaliserer kode du vil referere til native C-biblioteker eller Python-utvidelsesmoduler. 

For å støtte dem, og for å unngå dynamisk kompilering på hvert løp, kan du bygge din egen Cython forlengelsesmodul. Du må legge til en liten setup.py-fil og husk å bygge den før du kjører programmet når du endrer Cython-koden. Her er setup.py-filen:

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

Da må du bygge det:

$ python setup.py build_ext --inplace Kompilere pythagorean_triples.pyx fordi den endret seg. [1/1] Cythonizing pythagorean_triples.pyx kjører build_ext bygningen 'pythagorean_triples' utvidelse skape bygge skape build / temp.macosx-10.7-x86_64-3.6 gcc -Wno-ubrukt resultat -Wsign-compare -Wunreachable-code -DNDEBUG -g - fwrapv -O3 -Wall -Wstrict-prototyper -I / Brukere / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Brukere / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Brukere / gigi.sayfan / miniconda3 / envs / py3 / inkluderer / python3.6m -c pythagorean_triples.c -o bygge / 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 / Brukere / gigi.sayfan / miniconda3 / envs / py3 / lib -arch x86_64 bygge / 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

Som du kan se fra produksjonen, genererte Cython en C-fil kalt pythagorean_triples.c og kompilerer den en plattformspesifikk .so-fil, som er utvidelsesmodulen som Python nå kan importere som en hvilken som helst annen utvidelsesmodul. 

Hvis du er nysgjerrig, ta en titt på den genererte C-koden. Den er veldig lang (2789 linjer), stump, og inneholder mange ekstra ting som trengs for å jobbe med Python API. La oss slippe pyximporten og kjøre programmet vårt igjen:

importtid import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - start utskrift (resultat, varighet) hvis __name__ == '__main__': main 881 9.507064819335938 

Resultatet er ganske mye det samme som med pyximport. Vær imidlertid oppmerksom på at jeg bare måler kjøretiden til den cytoniserte koden. Jeg måler ikke hvor lenge det tar pyximport å kompilere den cytoniserte koden på fly. I store programmer kan dette være betydelig.

Legge til typer til koden din

La oss ta det til neste nivå. Cython er mer enn Python og legger til valgfri skriving. Her definerer jeg bare alle variablene som heltall, og ytelsen skyrockets:

# pythagorean_triples.pyx def count (grense): cdef int resultat = 0 cdef int a = 0 cdef int b = 0 cdef int c = 0 for et intervall (1 grense + 1): for b i området (a + 1 , grense + 1): for c i rekkevidde (b + 1, grense + 1): hvis c * c> a * a + b * b: bryte hvis c * c == (a * a + b * b): resultat + = 1 returresultat ---------- # main.py importtid import pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - start utskrift (resultat, varighet) hvis __name__ == '__main__': hoved () Utgang: 881 0,056414127349853516 

Ja. Det er riktig. Ved å definere et par heltall, går programmet på mindre enn 57 millisekunder, sammenlignet med mer enn 13 sekunder med ren Python. Det er nesten en 250X forbedring.

Profilering av koden din

Jeg brukte Pythons tidsmodul, som måler veggtid og er ganske bra mesteparten av tiden. Hvis du vil ha mer presis tidspunkt for små kodefragmenter, bør du vurdere å bruke timeit-modulen. Slik måler du kodenes ytelse ved hjelp av timeit:

>>> import timeit >>> timeit.timeit ('count (1000)', setup = "fra pythagorean_triples import count", tall = 1) 0.05357028398429975 # Kjører 10 ganger >>> timeit.timeit ('count (1000)' , setup = "fra pythagorean_triples import count", tall = 10) 0.5446877249924 

De timeit () funksjonen tar en uttalelse til å utføre, en oppsettkode som ikke måles, og antall ganger for å utføre den målte koden.

Avanserte emner

Jeg kladde bare overflaten her. Du kan gjøre mye mer med Cython. Her er noen emner som kan forbedre ytelsen til koden din ytterligere eller tillate Cython å integrere med andre miljøer:

  • kaller C-kode
  • interaksjon med Python C API og GIL
  • bruker C ++ i Python
  • dirigerer Cython-koden til PyPY
  • bruker parallellisme
  • Cython og NumPy
  • dele erklæringer mellom Cython moduler

Konklusjon

Cython kan produsere to størrelsesordener av ytelsesforbedring for svært liten innsats. Hvis du utvikler ikke-triviell programvare i Python, er Cython en no-brainer. Den har svært lite overhead, og du kan presentere det gradvis til kodebase.

I tillegg, ikke nøl med å se hva vi har tilgjengelig for salg og for studier på markedet, og ikke nøl med å stille spørsmål og gi din verdifulle tilbakemelding ved hjelp av feedet under.