Maison  >  Article  >  développement back-end  >  Utiliser des compilateurs JIT pour ralentir mes boucles Python ?

Utiliser des compilateurs JIT pour ralentir mes boucles Python ?

PHPz
PHPzoriginal
2024-08-29 06:35:07865parcourir

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.

Qu’est-ce que le JIT ?

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.

Construire la fonction de lissage exponentiel rapide

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 :

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

où :

  • StS_tSt  : Représente la valeur lissée à l'instant ttt .
  • VtV_tVt  : Représente la valeur d'origine à l'heure ttt à partir du tableau de valeurs.
  • αalphaα  : Le facteur de lissage, qui détermine le poids de la valeur actuelle VtV_tVt dans le processus de lissage.
  • St1S_{t-1}St−1  : Représente la valeur lissée à l'instant t1t-1t−1 , c'est-à-dire la valeur lissée précédente.

La formule applique un lissage exponentiel, où :

  • La nouvelle valeur lissée StS_tSt est une moyenne pondérée de la valeur actuelle VtV_tVt et la valeur lissée précédente St1S_{t-1}St−1 .
  • Le facteur αalphaα détermine l'influence de la valeur actuelle VtV_tVt a sur la valeur lissée par rapport à la valeur lissée précédente St1S_{t-1}St−1 .

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:

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!

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn