Snabbare Upp Python Med Cython

Cython är en superset av Python som gör att du kan förbättra hastigheten på din kod avsevärt. Du kan lägga till valfria typdeklarationer för ännu större fördelar. Cython översätter din kod till optimerad C / C ++ som kompileras till en Python-förlängningsmodul. 

I den här handledningen lär du dig hur du installerar Cython, få en omedelbar prestationsökning av din Python-kod gratis, och hur du verkligen utnyttjar Cython genom att lägga till typer och profilera din kod. Slutligen lär du dig mer avancerade ämnen som integration med C / C ++-kod och NumPy som du kan utforska ytterligare för ännu större vinster.

Räkna pythagoranska tripplar

Pythagoras var en grekisk matematiker och filosof. Han är känd för sin pythagoranska teorem, som säger att i en rätvinklig triangel är summan av kvadraterna på benen av trianglarna lika med hypotenusens torg. Pythagoranska tripplar är några tre positiva heltal a, b och c som sådant a² + b² = c². Här är ett program som hittar alla pythagoranska tripplarna vars medlemmar inte är större än den angivna gränsen.

importtid def count (limit): result = 0 för ett intervall (1, gräns + 1): för b inom intervallet (a + 1, gräns + 1): för c i intervallet (b + 1, gräns + 1) : om c * c> a * a + b * b: bryt om c * c == (a * a + b * b): resultat + = 1 returresultat om __name__ == '__main__': start = time.time () resultat = räkning (1000) varaktighet = tid.tid () - starta utskrift (resultat, varaktighet) Utgång: 881 13.883624076843262 

Tydligen finns det 881 tripplar, och det tog programmet lite mindre än 14 sekunder för att ta reda på det. Det är inte för länge, men tillräckligt länge för att vara irriterande. Om vi ​​vill hitta fler tripplar upp till en högre gräns, borde vi hitta ett sätt att göra det snabbare. 

Det visar sig att det finns väsentligt bättre algoritmer, men idag fokuserar vi på att göra Python snabbare med Cython, inte på bästa algoritm för att hitta pythagoranska tripplar. 

Enkel uppmuntran med pyximport

Det enklaste sättet att använda Cython är att använda den speciella pyximport-funktionen. Detta är ett uttalande som sammanställer din Cython-kod i flyg och låter dig njuta av fördelarna med inbyggd optimering utan att det är för mycket problem. 

Du måste ange koden för att syntetisera i sin egen modul, skriva en rad inställningar i huvudprogrammet och sedan importera det som vanligt. Låt oss se hur det ser ut. Jag flyttade funktionen till en egen fil som heter pythagorean_triples.pyx. Förlängningen är viktig för Cython. Linjen som aktiverar Cython är import pyximport; pyximport.install (). Sedan importerar den bara modulen med räkna() funktion och senare anropar den i huvudfunktionen.

importtid import pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - starta utskrift (resultat, varaktighet) om __name__ == '__main__' Huvud () Utgång: 881 9.432806253433228 

Den rena Python-funktionen sprang 50% längre. Vi fick denna ökning genom att lägga till en enda rad. Inte illa alls.

Bygg din egen förlängningsmodul

Medan pyximport verkligen är bekväm under utveckling, fungerar den bara på rena Python-moduler. Ofta när du optimerar kod du vill referera till infödda C-bibliotek eller Python-förlängningsmoduler. 

För att stödja dem, och för att undvika att dynamiskt kompilera på varje gång, kan du bygga din egen Cython-förlängningsmodul. Du måste lägga till en liten setup.py-fil och kom ihåg att bygga den innan du kör ditt program när du ändrar Cython-koden. Här är setup.py-filen:

från distutils.core import setup från Cython.Build import cythonize setup (ext_modules = cythonize ("pythagorean_triples.pyx"))

Då måste du bygga det:

$ python setup.py build_ext --inplace Kompilerar pythagorean_triples.pyx eftersom det ändrats. [1/1] Cythonizing pythagorean_triples.pyx running build_ext byggnad "pythagorean_triples" förlängning skapa bygg skapa build / temp.macosx-10.7-x86_64-3.6 gcc -Wno-oanvänd-resultat -Wsign-compare -Wunreachable-code -DNDEBUG -g - fwrapv -O3 -Wall -Wstrict-prototyper -I / Användare / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Användare / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Användare / gigi.sayfan / miniconda3 / envs / py3 / include / python3.6m -c pythagorean_triples.c -o bygg / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o gcc-bundle -undefined dynamic_lookup -L / Användare / gigi.sayfan / miniconda3 / envs / py3 / lib -L / Användare / gigi.sayfan / miniconda3 / envs / py3 / lib -arch x86_64 bygg / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o -L / Användare / gigi.sayfan / miniconda3 / envs / py3 / lib -o pythagorean_triples.cpython-36m-darwin.so

Som du kan se från utgången genererade Cython en C-fil som heter pythagorean_triples.c och sammanställer den en plattformsspecifik .so-fil, vilken är den förlängningsmodul som Python nu kan importera som någon annan inbyggd utvidgningsmodul. 

Om du är nyfiken, ta en titt på den genererade C-koden. Det är väldigt långt (2789 linjer), stumt och innehåller många extra saker som behövs för att arbeta med Python API. Låt oss släppa pyximporten och köra vårt program igen:

importtid import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - starta utskrift (resultat, duration) om __name__ == '__main__': main 881 9,507064819335938 

Resultatet är ganska mycket detsamma som med pyximport. Observera dock att jag mäter endast runtime för den cytoniserade koden. Jag mäter inte hur länge det tar pyximport att kompilera den cytoniserade koden i flygningen. I stora program kan detta vara betydande.

Lägger till typer i din kod

Låt oss ta den till nästa nivå. Cython är mer än Python och lägger till valfri typing. Här definierar jag bara alla variabler som heltal och prestandakryssarna:

# pythagorean_triples.pyx def count (gräns): cdef int resultat = 0 cdef int a = 0 cdef int b = 0 cdef int c = 0 för ett intervall (1, gräns + 1): för b i intervallet (a + 1 , gräns + 1): för c i intervallet (b + 1, gräns + 1): om c * c> a * a + b * b: bryt om 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 () - starta utskrift (resultat, varaktighet) om __name__ == '__main__' Huvud () Utgång: 881 0,056414127349853516 

Ja. Det stämmer. Genom att definiera ett par heltal går programmet på mindre än 57 millisekunder, jämfört med mer än 13 sekunder med ren Python. Det är nästan 250x förbättring.

Profilering av din kod

Jag använde Pythons tidsmodul, som mäter väggtiden och är ganska bra mest av tiden. Om du vill ha mer exakt timing av små kodfragment, överväg att använda tidsmodulen. Så här mäter du kodens prestanda med tiden:

>>> importtid >>> timeit.timeit ('count (1000)', setup = "från pythagorean_triples import count", nummer = 1) 0.05357028398429975 # Kör 10 gånger >>> timeit.timeit ('count (1000)' , setup = "från pythagorean_triples import count", nummer = 10) 0.5446877249924 

De timeit () funktionen tar ett uttalande att utföra, en inställningskod som inte mäts och hur många gånger som ska utföras för den uppmätta koden.

Avancerade ämnen

Jag har bara repat ytan här. Du kan göra mycket mer med Cython. Här är några ämnen som kan förbättra din kods prestanda ytterligare eller låta Cython integrera med andra miljöer:

  • kallar C-kod
  • interagerar med Python C API och GIL
  • använder C ++ i Python
  • portar Cython-kod till PyPY
  • använder parallellitet
  • Cython och NumPy
  • dela deklarationer mellan Cython-moduler

Slutsats

Cython kan producera två storleksordningar av prestandaförbättring för mycket liten ansträngning. Om du utvecklar icke-trivial programvara i Python är Cython en no-brainer. Det har mycket lite överhuvudtaget, och du kan introducera det gradvis till din kodbas.

Tveka inte att se vad vi har till salu och studera på marknaden, och tveka inte att ställa några frågor och ge din värdefulla feedback genom att använda foderet nedan.