+− THE DAILY DIFFdev & AI news
SHIP IT

Wydajność Pythona: natywne pętle NumPy, wywołania zwrotne i koszty konwersji

Kontrolowany test numeryczny tylko dla procesora śledzi identyczne wartości poprzez pętle CPython, arytmetykę NumPy, wrapper wywołania zwrotnego Pythona i skompilowaną pętlę Numba.

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

Kontrolowany test numeryczny tylko dla procesora śledzi identyczne wartości poprzez pętle CPython, arytmetykę NumPy, wrapper wywołania zwrotnego Pythona i skompilowaną pętlę Numba. NumPy z gotowym wejściem miał dużą przewagę, podczas gdy konwersja z i z powrotem do list Pythona zmniejszyła tę przewagę do niemal remisu. Odcinek wyjaśnia granicę wykonania i jak mierzyć wynik, którego potrzebuje aplikacja.

Przeczytaj wydanie pisemne (angielski) ↗

Co obejmuje ten film

  • CPython kompiluje kod źródłowy do kodu bajtowego. Wielokrotna praca interpretera/obiektu i natywna pętla numeryczna z typowaniem to różne sposoby wykonania.
  • Arytmetyka numeryczna NumPy może przekazywać typowane partie do skompilowanych operacji; pętla arytmetyczna nadal istnieje, a widoki mogą mieć nieciągłe układy.
  • Mierzenie konwersji i wymaganej reprezentacji wyjściowej. W tym uruchomieniu, konwersja list Pythona na tablice i z powrotem zniwelowała dużą przewagę gotowej tablicy.
  • Małe obciążenia mogą faworyzować zwykłego Pythona, ponieważ stałe koszty wywołania tablicy mają znaczenie. Składnia dla ośmiu wartości wygrała to mierzone porównanie.
  • np.vectorize to wygodny wrapper, który wywołuje Pythona dla kolejnych elementów; sam interfejs wsadowy nie ustanawia natywnego wykonania numerycznego.
  • Skompilowane pętle w składni Pythona również mogą być szybkie. Uwzględnij koszty zimnej kompilacji/konfiguracji, gdy mają one znaczenie dla wywołującego.
  • Natywne wykonanie, CPU SIMD, wielowątkowość i backendy GPU to oddzielne idee. Dostępne funkcje CPU nie śledzą instrukcji użytych w konkretnym uruchomieniu.

Przetłumaczona transkrypcja

Przetłumaczono z oryginalnej narracji angielskiej. Dostępne audio i napisy są kontrolowane przez YouTube.

Dlaczego ten sam procesor może skończyć szybciej?

0:00 Uważasz, że Python potrzebuje nowego języka, aby być szybkim. Ten sam procesor przetworzył naszą partię szybciej po przeniesieniu pętli. Dokąd poszła praca? Kiedy przyspieszenie znika? Który skrót przywraca Pythona? To jest The Daily Diff, od podszewki. Uważaj na ten skrót. CPython to standardowa implementacja Pythona.

0:17 Kompiluje kod źródłowy do kodu bajtowego, a następnie wykonuje te instrukcje w interpreterze. NumPy to biblioteka numeryczna, której tablice przechowują wartości z zadeklarowanym typem danych. Jej wbudowana arytmetyka może przekazać całą partię do skompilowanych operacji. Weź milion wartości zmiennoprzecinkowych, podnieś każdą do kwadratu, następnie dodaj wartość pierwotną. Każda wersja otrzymuje identyczne wartości i produkuje pasujące odpowiedzi. Wartości są wystarczająco małe, aby różnice zaokrągleń nie wpływały na wynik.

0:40 Na tej maszynie składnia lista zajęła około trzydziestu trzech milisekund. NumPy zajęło mniej niż jedną. Oba działały na procesorze. To przydatny dowód dla tego obciążenia i okropny nekrolog dla języka programowania. Zacznij od zwykłej pętli.

Jaką pracę powtarza pętla Pythona?

0:53 Python pobiera następną wartość, wykonuje operacje arytmetyczne i dodaje wynik. Następnie robi to ponownie. Mnożenie jest tanie. Organizowanie kolejnego mnożenia i obsługa obiektów Pythona wokół niego dodaje pracy za każdym razem. To jest faktyczna dezasemblacja testowanej funkcji. Iteracja, mnożenie i dodawanie pojawiają się jako instrukcje kodu bajtowego. Wersje i interpretery Pythona dokonują różnych wyborów.

1:14 Te czasy należą do wersji wyświetlanej na ekranie, czyli Pythona 3.10. Lista Pythona przechowuje referencje do obiektów, więc różne rodzaje rzeczy mogą dzielić listę. Nasze zadanie arytmetyczne wykorzystuje liczby w całym procesie. Płaci za ogólną aranżację, żądając jednocześnie powtarzalnej usługi. Wyobraź sobie zamawianie tej samej kawy milion razy i wypełnianie nowego formularza dla każdej filiżanki. Ekspres do kawy jest w porządku.

1:36 Dokumentacja rozwinęła własną karierę.

Gdzie NumPy przenosi pętlę?

1:38 NumPy zmienia umowę. Ta tablica przechowuje wartości zmiennoprzecinkowe o stałym rozmiarze z jednym zadeklarowanym typem. Nasza tablica jest ciągła, więc jej wartości leżą razem w pamięci. Widoki mogą mieć inne układy. Operacja arytmetyczna wie, jaki rodzaj danych otrzymuje. Wywołanie przekazuje partię do natywnej operacji. Pętla nadal odwiedza wartości wewnątrz skompilowanego kodu. Może ponownie wykorzystać wiedzę o typie w całej tej pracy.

2:00 Python pozostaje osobą przekazującą zadanie. Itamar Turner Trauring wyjaśnia to rozróżnienie w Python Speed. Interfejsy wsadowe, natywne implementacje i instrukcje wektorowe procesora to oddzielne rzeczy. Nazwa funkcji dostarcza bardzo mało dowodów.

Czy szybko oznacza GPU czy wiele rdzeni CPU?

2:13 Ten wynik użył NumPy na procesorze. Backend GPU, taki jak CuPy, wybiera inne miejsce dla tablic i operacji. Zakup GPU sprawia, że ta zwykła pętla Pythona działa tam, gdzie już działała. Instrukcje wektorowe procesora mogą przetwarzać kilka wartości jednocześnie. NumPy ma mechanizmy do wyboru obsługiwanych jąder. Nasz środowisko wykonawcze zgłasza dostępne funkcje, ale nie śledzi instrukcji tego wyrażenia. Przypisanie całego zmierzonego zysku do instrukcji wektorowych byłoby przesadzaniem dowodów.

2:40 Koszyk GPU może pozostać zamknięty. Jest coś głęboko satysfakcjonującego w naprawie wydajności, która również anuluje zakup. Niestety, benchmark trwa nadal.

Kiedy imponujące przyspieszenie znika?

2:49 Przy ośmiu wartościach zwyciężyła składnia listy. Wywołanie mechanizmów tablicy kosztowało więcej niż małe zadanie. Większe gotowe tablice wysunęły się na prowadzenie. Używaliśmy powtarzanych prób i rozgrzanego procesu. Nowsze kompilacje Pythona mogą zmienić punkt przecięcia. Następnie zmień, czego potrzebuje aplikacja. Zacznij od listy Pythona i zakończ nową listą Pythona. NumPy musi przekonwertować dane wejściowe na tablicę i zamienić odpowiedź z powrotem na obiekty Pythona.

3:11 Te kroki należą do rachunku, jeśli twoja aplikacja ich potrzebuje. Przy milionie wartości, ta kompletna ścieżka zajęła około dwudziestu ośmiu milisekund. Zrozumienie zajęło też około dwudziestu ośmiu. Nasza dramatyczna przewaga stała się niemal remisem. Szybka część była prawdziwa. Zaletą było pozostawienie danych w ich numerycznej formie.

Które granice należą do benchmarku?

3:28 Śledź dane. Jeśli następna operacja akceptuje tablicę, zachowaj tablicę. Jeśli wielokrotnie konwertujesz między listami a tablicami, zmierz te przejścia. Zatrzymanie przed kosztownym przekazaniem daje dokładne liczby dla niewłaściwego pytania. Pomnożenie tej tablicy tworzy tymczasowy wynik. Dodanie wartości oryginalnych tworzy wynik. Natywne wykonanie nadal potrzebuje pamięci i przesuwa dane.

3:47 Niektóre operacje akceptują bufor wyjściowy, zmniejszając alokacje, gdy twój kod może bezpiecznie go ponownie użyć. Jedna ładna linia może zamówić kilka dużych buforów. Python Speed również zwraca uwagę na ten problem. Alokator jest niezwykle obojętny na twoją estetykę.

Czy skompilowana pętla Pythona też może być szybka?

4:00 Możesz również zachować składnię pętli i zmienić jej wykonanie. Numba może kompilować obsługiwane numeryczne funkcje Pythona do kodu natywnego. Oto pętla z naszego testu z jej dekoratorem kompilatora. Nadal mówi 'dla'. Droga ścieżka interpretera uległa zmianie. Po kompilacji, ta pętla zajęła mniej niż pół milisekundy z gotowymi tablicami. Sama kompilacja zajęła w tym uruchomieniu około sekundę. Ten koszt konfiguracji został wykluczony z powtarzanych pomiarów czasu,

4:22 więc małe jednorazowe zadanie zasługuje na inne porównanie. Własny przewodnik Numba z przyjemnością pokazuje jawne pętle. Rozpoczęcie migracji języka przed znalezieniem gorącej ścieżki to skomplikowany sposób na unikanie otwierania profilera. Najpierw znajdź, co pochłania czas. Oczekiwanie na sieć, parsowanie obiektów i masowe operacje arytmetyczne wymagają różnych rozwiązań. Natywne operacje na tablicach pomagają, gdy obliczenia i dane pasują.

4:43 Zmierz wynik, którego faktycznie potrzebuje Twój wywołujący. Teraz o tym skrócie. Masz funkcję Pythona i chcesz, aby akceptowała tablicę. Wrapper o nazwie vectorize brzmi jak brakujący przycisk wydajności. Jego dokumentacja zawiera ważne zdanie czekające.

Co właściwie wykonuje np.vectorize?

4:56 NumPy twierdzi, że jego wrapper vectorize jest dostarczany przede wszystkim dla wygody. Jego implementacja to zasadniczo pętla for. Wrapper akceptuje partie, ale nadal wywołuje twoją funkcję Pythona dla kolejnych elementów. Interfejs stał się krótszy. Granica wykonania pozostała zajęta. Obserwuj te same wartości wracające przez wywołanie zwrotne Pythona. Z gotowymi tablicami, ta wersja zajęła około osiemdziesięciu siedmiu milisekund. Była wolniejsza niż nasze rozumienie.

5:18 Użyteczny wrapper do rozgłaszania może zachować dokładną pracę dla każdego elementu, którą próbowałeś usunąć. Sprawdź implementację stojącą za interfejsem wsadowym, utrzymuj kompatybilne dane w ich użytecznej formie i uwzględnij konwersję w porównaniu. Pętle natywne, wywołania zwrotne i skompilowane mogą wyglądać podobnie z perspektywy wywołującego. Nazwa vectorize złożyła obietnicę, której Twój stoper nigdy nie podpisał. Zachowaj ją, gdy potrzebujesz wygody. Dla wydajności, sprawdź pętlę, która działa.

Co powinieneś zmienić w swoim kodzie?

5:41 Mój werdykt to SHIP IT dla wsadowego przetwarzania mierzonej, numerycznej, gorącej ścieżki. Zachowaj zwykłe pętle, jeśli już dobrze działają. Sprawdź, gdzie odbywa się praca i czego potrzebuje następny wywołujący. Subskrybuj, naciśnij dzwonek i powiedz mi w komentarzach, czy Ty byś to inaczej ostemplował. I to jest na dzisiaj diff. Jestem Niko z Axrisi. Łącz odpowiedzialnie.

Źródła

  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

Powiązane filmy