dimanche, 14 avril 2024

Utiliser Cython pour accélérer l’itération du tableau dans NumPy

Crédit : pixaby

NumPy est compris pour être rapide, mais pourrait-il aller encore plus loin plus rapide? Voici comment utiliser Cython pour accélérer les itérations de plage dans NumPy.

NumPy offre aux utilisateurs de Python une bibliothèque extrêmement rapide pour travailler avec des données dans des matrices. Si vous voulez, par exemple, générer une matrice remplie de nombres aléatoires, vous pouvez le faire dans une partie du temps qu’il faudrait en Python traditionnel.

Pourtant, il y a des moments où même NumPy par lui-même n’est pas assez rapide. Si vous souhaitez apporter des améliorations aux matrices NumPy qui ne sont pas proposées dans l’API de NumPy, une approche normale consiste simplement à parcourir la matrice en Python … et à perdre tous les avantages de performances de l’utilisation de NumPy en premier lieu.

Heureusement, il existe une meilleure méthode pour travailler directement avec les informations NumPy : Cython. En composant du code Python annoté par type et en le compilant en C, vous pouvez répéter les variétés NumPy et travailler directement avec leurs données à la vitesse de C.

Ce court article passe en revue quelques concepts clés pour savoir comment utiliser Cython avec NumPy. Si vous ne connaissez pas actuellement Cython, consultez les principes de base de Cython et consultez ce didacticiel simple pour composer du code Cython.

Écrivez uniquement le code de calcul de base dans Cython pour NumPy

Le scénario le plus typique d’utilisation de Cython avec NumPy est celui où vous souhaitez prendre un tableau NumPy, le parcourir et effectuer des calculs sur chaque composant qui ne peuvent pas être effectués facilement dans NumPy.

Cython fonctionne en vous permettant de composer des modules dans une version annotée de type de Python, qui sont ensuite assemblés en C et importés dans votre script Python comme n’importe quel autre module.

Pour le dire simplement , vous écrivez quelque chose qui ressemble à une version Python de ce que vous souhaitez réaliser, puis l’accélérez en ajoutant des annotations qui permettent de l’assimiler au C.

À cette fin, vous devez simplement utilisez Cython pour la partie de votre programme qui effectue le calcul réel. Tout le reste qui n’est pas sensible aux performances, c’est-à-dire tout ce qui n’est pas réellement la boucle qui itère sur vos données, doit être écrit en Python normal.

Pourquoi faire cela ? Les modules Cython doivent être recompilés à chaque fois qu’ils sont modifiés, ce qui réduit le processus de développement. Vous ne souhaitez pas avoir besoin de recompiler vos modules Cython chaque fois que vous apportez des modifications qui ne concernent pas vraiment la partie de votre programme que vous essayez d’optimiser.

Répétez les sélections NumPy dans Cython , pas Python

La technique de base pour travailler efficacement avec NumPy dans Cython peut se résumer en trois actions :

  1. Écrire des fonctions dans Cython qui acceptent les tableaux NumPy en tant qu’éléments correctement saisis. Lorsque vous appelez la fonction Cython dans votre code Python, envoyez l’élément de plage NumPy entier comme argument pour cet appel de fonction.
  2. Effectuez toutes les itérations sur l’objet dans Cython.
  3. Renvoyer un tableau NumPy de votre module Cython à votre code Python.

Donc, ne faites pas quelque chose comme ça :

for index in len(numpy_array) : numpy_array [ index] = cython_function(numpy_array [index]

Au lieu de cela, faites quelque chose comme ceci :

return_numpy_array = cython_function(numpy_array) # dans cython : cdef cython_function(numpy_array) : pour l'élément dans numpy_array : ... return numpy_array

J’ai omis les détails de type et d’autres détails de ces exemples, mais la différence devrait être claire. La vraie version sur la plage NumPy doit être entièrement effectuée dans Cython, et non par des appels répétés à Cython pour chaque élément de la variété.

Transmettez efficacement les sélections NumPy typées aux fonctions Cython

Toutes les fonctions qui accèdent pt une sélection NumPy en tant qu’argument doit être correctement typée, afin que Cython sache comment interpréter l’argument comme une variété NumPy (rapide) plutôt qu’une chose Python générique (lent).

Voici un exemple de Instruction de fonction Cython qui prend une variété NumPy à deux dimensions :

def compute(int [:,::1] array_1) :

Dans la syntaxe « Python pur » de Cython, vous utiliserait cette annotation :

def calculate(array_1 : cython.int [:,::1] :

L’annotation int [] suggère un tableau d'entiers, éventuellement une plage NumPy. Mais pour être aussi précis que possible, nous devons suggérer le nombre de dimensions dans le tableau. Pour 2 dimensions, nous utiliserions int [:,:] ; pour 3, nous utiliserons int [:,:,:].

Nous devrions également suggérer la disposition de la mémoire pour la plage. Par défaut dans NumPy et Cython, les sélections sont définies dans un style adjacent adapté à C. ::1 est notre dernier élément dans l'exemple ci-dessus, nous utilisons donc int [:,:: 1] comme notre signature. (Pour plus de détails sur les autres choix de disposition de la mémoire, consultez les documents de Cython.)

Ces déclarations informent Cython non seulement qu'il s'agit de sélections NumPy, mais également sur la manière de les extraire de la manière la plus efficace possible.

Utilisez les vues de mémoire Cython pour un accès rapide aux plages NumPy

Cython a en fait une fonction appelée vues de mémoire typées qui vous offre un accès direct en lecture/écriture à de nombreux types de choses qui fonctionnent comme les sélections. Cela consiste en - vous l'avez pensé - des variétés NumPy.

Pour créer une vue mémoire, vous utilisez une syntaxe comparable aux déclarations de plage révélées ci-dessus :

# Cython conventionnel def compute( int [:,::1] array_1): cdef int [:,:] view2d = array_1 # mode Python pur def calculate(array_1: cython.int [:,::1]: view2d: int [:,:] = array_1

Notez que vous n'avez pas besoin de spécifier la conception de la mémoire dans l'instruction, car celle-ci se trouve immédiatement.

À partir de ce stade de votre code, vous lirez à partir de et écrivez dans view2d avec exactement la même syntaxe d'accès que vous le feriez pour l'objet array_1 (par exemple, view2d). Toutes les lectures et écritures sont effectuées directement à la région sous-jacente de la mémoire qui comprend la variété (encore une fois : rapide), au lieu d'utiliser les interfaces d'accès à l'objet (encore : lente).

Indexez, n'itérez pas, à travers Plages NumPy

Les utilisateurs de Python comprennent désormais la métaphore préférée pour parcourir gh les composants d'une choses sont pour élément dans élément :. Vous pouvez également utiliser cette métaphore dans Cython, mais elle ne donne pas la meilleure vitesse possible lorsque vous travaillez avec un tableau NumPy ou une vue mémoire. Pour cela, vous voudrez utiliser l'indexation de style C.

Voici un exemple d'utilisation de l'indexation pour les sélections NumPy :

# Cython conventionnel : cimport cython @cython. boundscheck(False) @cython. wraparound(False) def calculate(int [:,::1] array_1): # obtient les mesures maximales de la plage cdef Py_ssize_t x_max = array_1. forme [0] cdef Py_ssize_t y_max = array_1. shape [1] #créer une vue mémoire cdef int [:,:] vue2d = tableau_1 # accéder à la vue mémoire par la méthode de nos index contraints pour x dans variété(x_max): pour y dans variété(y_max): vue2d [x, y] = quelque chose() # mode pur-Python : importez cython @cython. boundscheck(False) @cython. wraparound(False) def compute(array_1: cython.int [:,::1] : # obtenir les mesures maximales de la sélection x_max : cython.size _ t = array_1. shape [0] y_max : cython.size _ t = array_1. shape [1] #créer une memoryview view2d: int [:,:] = array_1 # accéder à la memoryview par la méthode de nos index contraints for x in range(x_max): for y in journey(y_max): view2d [x, y] = quelque chose()

Dans cet exemple, nous utilisons le crédit . shape de la variété NumPy pour acquérir ses mesures. Nous utilisons ensuite variety() à répéter dans la vue mémoire avec ces mesures comme contrainte. Nous n'autorisons pas l'accès arbitraire à une partie de la plage, par exemple, par la méthode d'une variable soumise par l'utilisateur, il n'y a donc aucun risque de sortir des limites.

Vous remarquerez également que nous avons des concepteurs @cython. boundscheck(False) et @cython. wraparound(False) sur nos fonctions.

Par défaut, Cython autorise des alternatives qui évitent les erreurs es avec des accesseurs variés, de sorte que vous ne finissiez pas par lire en dehors des limites d'une sélection par erreur.

Les vérifications ralentissent cependant l'accès à la sélection, du fait que chaque opération doit être vérifiée dans les limites. L'utilisation des décorateurs désactive ces gardes en les rendant inutiles. Nous avons en fait déjà déterminé quelles sont les limites de la plage, et nous ne les dépassons pas.

.

Toute l'actualité en temps réel, est sur L'Entrepreneur

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici