Python har opnået et ry for at være kraftfuldt, fleksibelt og nemt at arbejde med. Disse dyder har ført til, at det anvendes i et stort og voksende udvalg af applikationer, arbejdsgange og områder. Men sprogets design – dets fortolkede karakter, dets dynamik ved kørselstid – betyder, at Python altid har været en størrelsesorden langsommere end maskin-native sprog som C eller C++.
I årenes løb har udviklerne fundet på en række løsninger på Pythons hastighedsbegrænsninger. Man kan f.eks. skrive ydelsesintensive opgaver i C og pakke dem ind i Python; mange maskinlæringsbiblioteker gør netop dette. Eller du kunne bruge Cython, et projekt, der giver dig mulighed for at drysse Python-kode med runtime-typeoplysninger, der gør det muligt at kompilere den til C.
Men løsninger er aldrig ideelle. Ville det ikke være fantastisk, hvis vi bare kunne tage et eksisterende Python-program som det er, og køre det dramatisk hurtigere? Det er præcis, hvad PyPy giver dig mulighed for.
PyPyPy vs. CPython
PyPyPy er en drop-in-erstatning for den almindelige Python-fortolker, CPython. Mens CPython kompilerer Python til mellemliggende bytekode, som derefter fortolkes af en virtuel maskine, bruger PyPy just-in-time-kompilering (JIT) til at oversætte Python-kode til maskinnativt assemblagesprog.
Afhængigt af den opgave, der udføres, kan ydelsesforbedringerne være dramatiske. I gennemsnit fremskynder PyPy Python med ca. 7,6 gange, og nogle opgaver accelereres 50 gange eller mere. CPython-fortolkeren udfører simpelthen ikke de samme former for optimeringer som PyPy, og det vil den sandsynligvis aldrig gøre, da det ikke er et af dens designmål.
Det bedste er, at der kun kræves en lille eller ingen indsats fra udviklerens side for at frigøre de gevinster, PyPy giver. Man skal blot udskifte CPython med PyPy, og så er man for det meste færdig. Der er nogle få undtagelser, som gennemgås nedenfor, men PyPys erklærede mål er at køre eksisterende, uændret Python-kode og give den en automatisk hastighedsforøgelse.
PyPyPy understøtter i øjeblikket både Python 2 og Python 3, ved hjælp af forskellige inkarnationer af projektet. Med andre ord skal du downloade forskellige versioner af PyPy afhængigt af den version af Python, du skal køre. Python 2-grenen af PyPy har eksisteret i meget længere tid, men Python 3-versionen er blevet opdateret på det seneste. Den understøtter i øjeblikket både Python 3.5 (produktionskvalitet) og Python 3.6 (betakvalitet).
Ud over at understøtte alle Python-sprogets kerneelementer fungerer PyPy med langt de fleste værktøjer i Python-økosystemet, f.eks. pip
til pakning eller virtualenv
til virtuelle miljøer. De fleste Python-pakker, selv dem med C-moduler, bør fungere som de er, selv om der er begrænsninger, som vi kommer ind på nedenfor.
Hvordan PyPy fungerer
PyPyPy bruger optimeringsteknikker, som findes i andre just-in-time-kompilere til dynamiske sprog. Den analyserer kørende Python-programmer for at bestemme typeoplysningerne for objekter, mens de oprettes og bruges i programmerne, og bruger derefter disse typeoplysninger som en vejledning til at fremskynde tingene. Hvis en Python-funktion f.eks. kun arbejder med en eller to forskellige objekttyper, genererer PyPy maskinkode til at håndtere disse specifikke tilfælde.
PyPyPys optimeringer håndteres automatisk på køretid, så du behøver generelt ikke at justere dens ydeevne. En avanceret bruger kan eksperimentere med PyPys kommandolinjeindstillinger for at generere hurtigere kode til særlige tilfælde, men det er kun sjældent nødvendigt.
PyPyPy afviger også fra den måde CPython håndterer nogle interne funktioner på, men forsøger at bevare en kompatibel adfærd. PyPy håndterer f.eks. garbage collection anderledes end CPython. Ikke alle objekter indsamles straks, når de går ud af anvendelsesområdet, så et Python-program, der kører under PyPy, kan vise et større hukommelsesaftryk, end når det kører under CPython. Men du kan stadig bruge Pythons garbage collection-kontroller på højt niveau, der udstilles via modulet gc
, såsom gc.enable()
, gc.disable()
og gc.collect()
.
Hvis du ønsker oplysninger om PyPys JIT-adfærd på køretid, indeholder PyPy et modul, pypyjit
, der udsætter mange JIT-kroge til dit Python-program. Hvis du har en funktion eller et modul, der ser ud til at yde dårligt med JIT, giver pypyjit
dig mulighed for at få detaljerede statistikker om det.
Et andet PyPy-specifikt modul, __pypy__
, eksponerer andre funktioner, der er specifikke for PyPy, så det kan være nyttigt for at skrive programmer, der udnytter disse funktioner. På grund af Pythons kørselstidsdynamik er det muligt at konstruere Python-apps, der bruger disse funktioner, når PyPy er til stede, og ignorerer dem, når det ikke er til stede.
PyPyPy-begrænsninger
Meget hvor magisk PyPy end kan virke, er det ikke magi. PyPy har visse begrænsninger, der reducerer eller udelukker dens effektivitet for visse typer programmer. Desværre er PyPy ikke en fuldstændig universel erstatning for den standard CPython-køretid.
PyPyPy fungerer bedst med rene Python-programmer
PyPyPy har altid fungeret bedst med “rene” Python-programmer – dvs. programmer, der er skrevet i Python og intet andet. Python-pakker, der har grænseflader til C-biblioteker, f.eks. NumPy, har ikke klaret sig så godt på grund af den måde, hvorpå PyPy emulerer CPythons native binære grænseflader.
PyPys udviklere har pillet ved dette problem og gjort PyPy mere kompatibel med de fleste Python-pakker, der er afhængige af C-udvidelser. Numpy fungerer f.eks. meget godt med PyPy nu. Men hvis du vil have maksimal kompatibilitet med C-udvidelser, skal du bruge CPython.
PyPyPy fungerer bedst med programmer, der kører længere
En af bivirkningerne ved den måde, hvorpå PyPy optimerer Python-programmer, er, at programmer, der kører længere, har mest gavn af dens optimeringer. Jo længere programmet kører, jo flere typeoplysninger om kørselstid kan PyPy indsamle, og jo flere optimeringer kan den foretage. Python-scripts, der kun udføres én gang, vil ikke drage fordel af denne slags ting. De programmer, der har gavn af det, har typisk løkker, der kører i længere tid eller kører kontinuerligt i baggrunden – f.eks. webframeworks.
PyPyPy laver ikke ahead-of-time-kompilering
PyPyPy kompilerer Python-kode, men det er ikke en kompiler til Python-kode. På grund af den måde, hvorpå PyPy udfører sine optimeringer, og den iboende dynamik i Python, er der ingen måde at udstede den resulterende JIT-tede kode som en standalone binær fil og genbruge den. Hvert program skal kompileres for hver kørsel. Hvis du ønsker at kompilere Python til hurtigere kode, der kan køre som en standalone-app, skal du bruge Cython, Numba eller det i øjeblikket eksperimentelle Nuitka-projekt.