+− THE DAILY DIFFdev & AI news
SHIP IT

Python-prestanda: interna NumPy-loopar, callbacks och konverteringskostnader

Ett kontrollerat numeriskt test som endast använder CPU följer identiska värden genom CPython-loopar, NumPy-aritmetik, en Python-callback-wrapper och en kompilerad Numba-loop.

Want to see our stories first on Google? Add us to your preferred sources — it helps us grow.

Ett kontrollerat numeriskt test som endast använder CPU följer identiska värden genom CPython-loopar, NumPy-aritmetik, en Python-callback-wrapper och en kompilerad Numba-loop. NumPy med färdig inmatning hade ett stort försprång, medan konvertering från och tillbaka till Python-listor minskade fördelen till nästan oavgjort. Avsnittet förklarar exekveringsgränsen och hur man mäter det resultat en applikation behöver.

Läs den skrivna upplagan (engelska) ↗

Vad den här videon täcker

  • CPython kompilerar källkod till bytecode. Upprepat interpreter/objektarbete och en typad intern numerisk loop är olika exekveringsarrangemang.
  • Numerisk NumPy-aritmetik kan överlämna typade batchar till kompilerade operationer; den aritmetiska loopen existerar fortfarande, och vyer kan ha icke-sammanhängande layouter.
  • Mät konvertering och nödvändig utdata representation. I denna körning raderade konvertering av Python-listor till arrayer och tillbaka den stora fördelen med färdiga arrayer.
  • Små arbetsbelastningar kan gynna vanlig Python eftersom fasta array-anropskostnader spelar roll. Listkomprehensionen med åtta värden vann denna uppmätta jämförelse.
  • np.vectorize är en bekvämlighets-wrapper som anropar Python för successiva element; ett batch-gränssnitt etablerar inte ensam intern numerisk exekvering.
  • Kompilerade loopar med Python-syntax kan också vara snabba. Inkludera kostnader för kall kompilering/installation när de är relevanta för den som anropar.
  • Intern exekvering, CPU SIMD, multithreading och GPU-backend är separata idéer. Tillgängliga CPU-funktioner spårar inte de instruktioner som används i en specifik körning.

Översatt transkription

Översatt från den ursprungliga engelska berättelsen. Tillgängligt ljud och undertexter styrs av YouTube.

Varför kan samma CPU bli klar snabbare?

0:00 Du tror att Python behöver ett nytt språk för att bli snabbt. Samma CPU körde vår batch snabbare efter att loopen flyttades. Vart tog arbetet vägen? När försvinner hastighetsökningen? Vilken genväg tar Python tillbaka? Detta är The Daily Diff, under huven. Håll ögonen på den genvägen. CPython är standardimplementeringen av Python.

0:17 Den kompilerar din källkod till bytecode och kör sedan instruktionerna i en interpreter. NumPy är ett numeriskt bibliotek vars arrayer lagrar värden med en deklarerad datatyp. Dess inbyggda aritmetik kan överlämna en hel batch till kompilerade operationer. Ta en miljon flyttalsvärden, kvadrera varje värde, lägg sedan till det ursprungliga värdet. Varje version får identiska värden och producerar matchande svar. Värdena är tillräckligt små för att hålla avrundningsskillnader utanför tävlingen.

0:40 På den här maskinen tog listkomprehensionen cirka trettiotre millisekunder. NumPy tog mindre än en. Båda kördes på CPU:n. Det är användbara bevis för denna arbetsbelastning, och en fruktansvärd dödsruna för ett programmeringsspråk. Börja med den vanliga loopen.

Vilket arbete upprepar en Python-loop?

0:53 Python hämtar nästa värde, utför aritmetiken och lägger till svaret. Sedan gör den det igen. Multiplikationen är billig. Att arrangera nästa multiplikation och hantera Python-objekten runt den, lägger till arbete varje gång. Detta är den testade funktionens faktiska demontering. Iteration, multiplikation och addition visas som bytecode-instruktioner. Python-versioner och tolkar gör olika val.

1:14 Dessa tider tillhör den version som visas på skärmen, som är Python 3.10. En Python-lista lagrar referenser till objekt, så olika typer av saker kan dela en lista. Vårt aritmetiska jobb använder genomgående tal. Den betalar för ett allmänt arrangemang samtidigt som den begär en repetitiv tjänst. Föreställ dig att beställa samma kaffe en miljon gånger och fylla i ett nytt formulär för varje kopp. Kaffemaskinen fungerar bra.

1:36 Pappersarbetet har utvecklat en egen karriär.

Vart flyttar NumPy loopen?

1:38 NumPy ändrar kontraktet. Denna array innehåller flyttalsvärden med fast storlek och en deklarerad typ. Vår array är sammanhängande, så dess värden ligger tillsammans i minnet. Vyer kan ha andra layouter. Den aritmetiska operationen vet vilken typ av data den tar emot. Anropet överlämnar batchen till en intern operation. En loop besöker fortfarande värdena inuti kompilerad kod. Den kan återanvända kunskap om typen i det arbetet.

2:00 Python förblir den som överlämnar jobbet. Itamar Turner Trauring förklarar skillnaden i Python Speed. Batch-gränssnitt, interna implementeringar och processorvektorinstruktioner är skilda saker. Funktionsnamnet ger väldigt lite bevis.

Betyder snabbt GPU eller många CPU-kärnor?

2:13 Detta resultat använde NumPy på CPU:n. En GPU-backend som CuPy väljer ett annat hem för arrayer och operationer. Att köpa en GPU lämnar denna vanliga Python-loop körande där den redan kördes. CPU-vektorinstruktioner kan bearbeta flera värden tillsammans. NumPy har mekanismer för att välja stödda kärnor. Vår körtid rapporterar tillgängliga funktioner, men spårar inte detta uttrycks instruktioner. Att tillskriva hela den uppmätta vinsten till vektorinstruktioner skulle övervärdera bevisen.

2:40 GPU-kundvagnen kan förbli stängd. Det är något djupt tillfredsställande med en prestandaförbättring som också avbryter ett köp. Tyvärr fortsätter benchmarken.

När försvinner den imponerande hastighetsökningen?

2:49 Med åtta värden vann listkomprehensionen. Att anropa arraymekanismen kostade mer än det lilla jobbet. Större färdiga arrayer drog ifrån. Vi använde upprepade försök och en uppvärmd process. Nyare Python-byggen kan ändra övergångspunkten. Ändra sedan vad applikationen behöver. Börja med en Python-lista och sluta med en ny Python-lista. NumPy måste konvertera inmatningen till en array och omvandla svaret tillbaka till Python-objekt.

3:11 Dessa steg tillhör räkningen om din applikation behöver dem. Vid en miljon värden tog den fullständiga vägen cirka tjugoåtta millisekunder. Listkomprehensionen tog också cirka tjugoåtta. Vårt dramatiska försprång blev nästan oavgjort. Den snabba delen var verklig. Fördelen berodde på att data fick stanna i sin numeriska form.

Vilka gränser hör hemma i benchmarken?

3:28 Följ datan. Om nästa operation accepterar en array, behåll arrayen. Om du upprepade gånger konverterar mellan listor och arrayer, mät dessa övergångar. Att stoppa före en dyr överlämning ger exakta siffror för den felaktiga frågan. Att multiplicera denna array skapar ett temporärt resultat. Att lägga till originalvärdena skapar utdata. Native exekvering behöver fortfarande minne och flyttar data.

3:47 Vissa operationer accepterar en utdatabuffert, vilket minskar allokeringar när din kod säkert kan återanvända den. En snygg rad kan beställa flera stora buffertar. Python Speed lyfter också fram detta problem. Allokatören är anmärkningsvärt likgiltig för din estetik.

Kan en kompilerad Python-loop också vara snabb?

4:00 Du kan också behålla loop-syntax och ändra dess exekvering. Numba kan kompilera stödda numeriska Python-funktioner till intern kod. Här är loopen från vårt test med dess kompilatordekoratör. Det står fortfarande 'for'. Den dyra interpretervägen har ändrats. Efter kompilering tog denna loop mindre än en halv millisekund med färdiga arrayer. Kompileringen i sig tog cirka en sekund i denna körning. Denna installationskostnad exkluderades från de upprepade tiderna,

4:22 så ett litet engångsjobb förtjänar en annan jämförelse. Numbas egen guide visar gärna explicita loopar. Att påbörja en språkflytt innan man hittat den "heta sökvägen" är ett invecklat sätt att undvika att öppna en profilerare. Hitta först vad som förbrukar tiden. Nätverksväntan, objektparsning och bulk-aritmetik kräver olika lösningar. Inbyggda array-operationer hjälper när beräkningen och datan passar.

4:43 Mät det resultat din anropare faktiskt behöver. Nu till den genvägen. Du har en Python-funktion och vill att den ska acceptera en array. En wrapper som heter vectorize låter som den saknade prestandaknappen. Dess dokumentation innehåller en viktig mening som väntar.

Vad exekverar np.vectorize egentligen?

4:56 NumPy säger att dess vectorize-wrapper främst tillhandahålls för bekvämlighet. Dess implementering är i huvudsak en for-loop. Wrappern accepterar batchar, men fortsätter att anropa din Python-funktion för successiva element. Gränssnittet blev kortare. Exekveringsgränsen förblev upptagen. Se samma värden komma tillbaka genom Python-callbacken. Med färdiga arrayer tog denna version cirka åttiosju millisekunder. Den var långsammare än vår listkomprehension.

5:18 En användbar broadcasting-wrapper kan bevara det exakta arbetet per element som du försökte ta bort. Kontrollera implementeringen bakom ett batch-gränssnitt, behåll kompatibla data i sin användbara form, och inkludera konvertering i jämförelsen. Native, callback och kompilerade loopar kan se liknande ut från anroparens perspektiv. Namnet vectorize gav ett löfte som ditt stoppur aldrig skrev under på. Behåll det när bekvämlighet är vad du behöver. För prestanda, kontrollera loopen som körs.

Vad bör du ändra i din kod?

5:41 Min dom är SHIP IT för batching av en uppmätt numerisk hot path. Behåll vanliga loopar när de redan fungerar bra. Kontrollera var arbetet sker och vad nästa anropare behöver. Prenumerera, tryck på klockan och berätta i kommentarerna om du hade stämplat det annorlunda. Och det var diffen för idag. Jag är Niko från Axrisi. Sammanfoga ansvarsfullt.

Källor

  1. Universal functions (ufunc) basics — NumPy 2.2NumPy
  2. The N-dimensional array — NumPy 2.2NumPy
  3. numpy.vectorize — convenience and performance notesNumPy
  4. CPU/SIMD optimizations — NumPy 2.2NumPy
  5. dis — Disassembler for Python bytecodePython Software Foundation
  6. CPython 3.10.12 list representationCPython contributors
  7. The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
  8. Performance TipsNumba
  9. Basics of CuPyCuPy

Relaterade videor