Maison > Article > développement back-end > Utiliser des compilateurs JIT pour ralentir mes boucles Python ?
Si vous ne l'avez pas entendu, les boucles Python peuvent être lentes, en particulier lorsque vous travaillez avec de grands ensembles de données. Si vous essayez d'effectuer des calculs sur des millions de points de données, le temps d'exécution peut rapidement devenir un goulot d'étranglement. Heureusement pour nous, Numba dispose d'un compilateur Just-in-Time (JIT) que nous pouvons utiliser pour accélérer nos calculs numériques et nos boucles en Python.
L'autre jour, je me suis retrouvé dans le besoin d'une simple fonction de lissage exponentiel en Python. Cette fonction devait prendre un tableau et renvoyer un tableau de même longueur avec les valeurs lissées. En règle générale, j'essaie d'éviter les boucles autant que possible en Python (en particulier lorsqu'il s'agit de Pandas DataFrames). À mon niveau actuel de capacités, je ne voyais pas comment éviter d'utiliser une boucle pour lisser de manière exponentielle un tableau de valeurs.
Je vais parcourir le processus de création de cette fonction de lissage exponentiel et la tester avec et sans la compilation JIT. J'aborderai brièvement JIT et comment je me suis assuré de coder la boucle d'une manière qui fonctionnait avec le mode nopython.
Les compilateurs JIT sont particulièrement utiles avec les langages de niveau supérieur comme Python, JavaScript et Java. Ces langages sont connus pour leur flexibilité et leur facilité d'utilisation, mais ils peuvent souffrir de vitesses d'exécution plus lentes que celles des langages de niveau inférieur comme C ou C++. La compilation JIT aide à combler cette lacune en optimisant l'exécution du code au moment de l'exécution, le rendant ainsi plus rapide sans sacrifier les avantages de ces langages de niveau supérieur.
Lors de l'utilisation du mode nopython=True dans le compilateur Numba JIT, l'interpréteur Python est entièrement contourné, obligeant Numba à tout compiler en code machine. Cela se traduit par une exécution encore plus rapide en éliminant la surcharge associée au typage dynamique de Python et à d'autres opérations liées à l'interpréteur.
Le lissage exponentiel est une technique utilisée pour lisser les données en appliquant une moyenne pondérée aux observations passées. La formule du lissage exponentiel est :
où :
La formule applique un lissage exponentiel, où :
Pour implémenter cela en Python et nous en tenir aux fonctionnalités qui fonctionnent avec le mode nopython=True, nous transmettrons un tableau de valeurs de données et le flottant alpha. Par défaut, l'alpha est 0,33333333 car cela correspond à mon cas d'utilisation actuel. Nous allons initialiser un tableau vide pour stocker les valeurs lissées, boucler et calculer, et renvoyer les valeurs lissées. Voilà à quoi ça ressemble :
@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
Simple, non ? Voyons si JIT fait quelque chose maintenant. Tout d’abord, nous devons créer un large tableau d’entiers. Ensuite, nous appelons la fonction, chronométrons le temps qu'il a fallu pour calculer et imprimons les résultats.
# 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:
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!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!