Heim  >  Artikel  >  Backend-Entwicklung  >  Verwenden Sie JIT-Compiler, um meine Python-Schleifen langsamer zu machen?

Verwenden Sie JIT-Compiler, um meine Python-Schleifen langsamer zu machen?

PHPz
PHPzOriginal
2024-08-29 06:35:07865Durchsuche

Falls Sie es noch nicht gehört haben: Python-Schleifen können langsam sein – insbesondere bei der Arbeit mit großen Datensätzen. Wenn Sie Berechnungen über Millionen von Datenpunkten durchführen möchten, kann die Ausführungszeit schnell zu einem Engpass werden. Zum Glück verfügt Numba über einen Just-in-Time-Compiler (JIT), mit dem wir unsere numerischen Berechnungen und Schleifen in Python beschleunigen können.

Neulich brauchte ich eine einfache exponentielle Glättungsfunktion in Python. Diese Funktion musste ein Array aufnehmen und ein Array derselben Länge mit den geglätteten Werten zurückgeben. Normalerweise versuche ich, Schleifen in Python nach Möglichkeit zu vermeiden (insbesondere beim Umgang mit Pandas DataFrames). Auf meinem derzeitigen Leistungsstand habe ich nicht gesehen, wie ich die Verwendung einer Schleife zum exponentiellen Glätten eines Wertearrays vermeiden kann.

Ich werde den Prozess der Erstellung dieser exponentiellen Glättungsfunktion durchgehen und sie mit und ohne JIT-Kompilierung testen. Ich werde kurz auf JIT eingehen und wie ich dafür gesorgt habe, dass die Schleife so codiert wurde, dass sie mit dem Nopython-Modus funktioniert.

Was ist JIT?

JIT-Compiler sind besonders nützlich bei höheren Sprachen wie Python, JavaScript und Java. Diese Sprachen sind für ihre Flexibilität und Benutzerfreundlichkeit bekannt, können jedoch im Vergleich zu Sprachen niedrigerer Ebenen wie C oder C++ unter langsameren Ausführungsgeschwindigkeiten leiden. Die JIT-Kompilierung hilft, diese Lücke zu schließen, indem sie die Ausführung des Codes zur Laufzeit optimiert und ihn schneller macht, ohne auf die Vorteile dieser höheren Sprachen zu verzichten.

Bei Verwendung des nopython=True-Modus im Numba-JIT-Compiler wird der Python-Interpreter vollständig umgangen, sodass Numba gezwungen ist, alles bis hin zum Maschinencode zu kompilieren. Dies führt zu einer noch schnelleren Ausführung, da der mit der dynamischen Typisierung von Python und anderen interpreterbezogenen Vorgängen verbundene Overhead entfällt.

Aufbau der schnellen exponentiellen Glättungsfunktion

Exponentielle Glättung ist eine Technik zur Glättung von Daten durch Anwendung eines gewichteten Durchschnitts über vergangene Beobachtungen. Die Formel für die exponentielle Glättung lautet:

St=αVt+(1α)St1 S_t = Alpha cdot V_t + (1 - Alpha) cdot S_{t-1} St=α⋅V t+(1−α)⋅St−1

wo:

  • StS_tSt : Stellt den geglätteten Wert zum Zeitpunkt dar ttt .
  • VtV_tVt : Stellt den ursprünglichen Wert zum Zeitpunkt dar ttt aus dem Wertearray.
  • αalphaα : Der Glättungsfaktor, der das Gewicht des aktuellen Werts bestimmt VtV_tVt im Glättungsprozess.
  • St1S_{t-1}St−1 : Stellt den geglätteten Wert zum Zeitpunkt dar t1t-1t−1 , d. h. der vorherige geglättete Wert.

Die Formel wendet eine exponentielle Glättung an, wobei:

  • Der neue geglättete Wert StS_tSt ist ein gewichteter Durchschnitt des aktuellen Wertes VtV_tVt und der vorherige geglättete Wert St1S_{t-1}St−1 .
  • Der Faktor αalphaα bestimmt, wie stark der aktuelle Wert beeinflusst wird VtV_tVt hat auf dem geglätteten Wert im Vergleich zum vorherigen geglätteten Wert St1S_{t-1}St−1 .

Um dies in Python zu implementieren und die Funktionalität beizubehalten, die mit dem Modus „nopython=True“ funktioniert, übergeben wir ein Array von Datenwerten und den Alpha-Float. Ich verwende standardmäßig den Alphawert 0,33333333, da dies zu meinem aktuellen Anwendungsfall passt. Wir werden ein leeres Array initialisieren, um die geglätteten Werte darin zu speichern, eine Schleife zu erstellen, zu berechnen und geglättete Werte zurückzugeben. So sieht es aus:

@jit(nopython=True) 
def fast_exponential_smoothing(values, alpha=0.33333333): 

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
    return smoothed_values

Einfach, oder? Mal sehen, ob JIT jetzt etwas tut. Zuerst müssen wir ein großes Array von Ganzzahlen erstellen. Dann rufen wir die Funktion auf, messen die Berechnungsdauer und geben die Ergebnisse aus.

# Generate a large random array of a million integers
large_array = np.random.randint(1, 100, size=1_000_000)

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

This can be repeated and altered just a bit to test the function without the JIT decorator. Here are the results that I got:

Using JIT-compilers to make my Python loops slower?

Wait, what the f***?

I thought JIT was supposed to speed it up. It looks like the standard Python function beat the JIT version and a version that attempts to use no recursion. That's strange. I guess you can't just slap the JIT decorator on something and make it go faster? Perhaps simple array loops and NumPy operations are already pretty efficient? Perhaps I don't understand the use case for JIT as well as I should? Maybe we should try this on a more complex loop?

Here is the entire code python file I created for testing:

import numpy as np
from numba import jit
import time

@jit(nopython=True) 
def fast_exponential_smoothing(values, alpha=0.33333333): 

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
        return smoothed_values

def fast_exponential_smoothing_nojit(values, alpha=0.33333333):

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
        return smoothed_values

def non_recursive_exponential_smoothing(values, alpha=0.33333333):
    n = len(values)
    smoothed_values = np.zeros(n)

    # Initialize the first value
    smoothed_values[0] = values[0]

    # Calculate the rest of the smoothed values
    decay_factors = (1 - alpha) ** np.arange(1, n)
    cumulative_weights = alpha * decay_factors
    smoothed_values[1:] = np.cumsum(values[1:] * np.flip(cumulative_weights)) + (1 - alpha) ** np.arange(1, n) * values[0]

    return smoothed_values

# Generate a large random array of a million integers
large_array = np.random.randint(1, 1000, size=10_000_000)

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing_nojit(large_array)
end_time = time.time()
print(f"Exponential Smoothing without JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = non_recursive_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with no recursion or JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

I attempted to create the non-recursive version to see if vectorized operations across arrays would make it go faster, but it seems to be pretty damn fast as it is. These results remained the same all the way up until I didn't have enough memory to make the array of random integers.

Let me know what you think about this in the comments. I am by no means a professional developer, so I am accepting all comments, criticisms, or educational opportunities.

Until next time.

Happy coding!

Das obige ist der detaillierte Inhalt vonVerwenden Sie JIT-Compiler, um meine Python-Schleifen langsamer zu machen?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn