Lorsqu'il est implémenté en Python, le temps de traitement peut être trop long pour répondre aux exigences. Dans ce cas, quatre types de contre-mesures sont présentés dans cet article.
Si vous faites quelque chose pour accélérer les choses, vous perdez autre chose. Des exemples typiques de compromis sont la liberté d'expression, la lisibilité, la dépendance, l'utilisation de la mémoire et l'utilisation du processeur. Veuillez suivre l'utilisation et la capacité et l'utiliser correctement.
De plus, avant d'accélérer, il est nécessaire de vérifier ce qui est lent dans le script à l'aide d'un outil de profilage ou autre. Même si vous faites de votre mieux pour accélérer le traitement qui n'est pas lent, cela aura peu d'effet sur le temps d'exécution global. Cet article n'explique pas la procédure.
Dans cet article, nous présenterons les quatre types de méthodes d'accélération suivants.
À l'origine, d'autres mesures telles que «utiliser une bibliothèque» et «créer une bibliothèque» correspondent également à «réécrire un script». Ici, la [Spécification du langage] de Python (https://docs.python.org/ja/3/reference/index.html) et la Bibliothèque standard Les méthodes suivantes sont présentées comme des choses qui peuvent être effectuées dans la catégorie /index.html). Cela n'ajoute pas grand-chose à la dépendance (bien que cela puisse dépendre de la version Python) comme cela peut être fait dans la catégorie bibliothèque standard.
L'optimisation de l'interpréteur Python ne fait pas grand-chose. Il existe de nombreuses façons de faire la même chose, vous pouvez donc choisir un moyen plus rapide de l'accélérer.
Cependant, dans une application réelle, la différence de style d'écriture donnée ici ne fait pas beaucoup de différence. Si vous ne pouvez pas penser à une conversion, ou si le traitement lent est susceptible d'être ailleurs.
Bien sûr, si vous accumulez de la poussière, vous pouvez le faire. Par exemple, si vous essayez d'implémenter une base de données en utilisant uniquement Python, la lenteur du traitement comme indiqué ici sera efficace, et elle sera inutilisable dans son ensemble.
Par exemple, le processus d'ajout d'entiers de 1 à n est "(1) Add using for" "(2) Built-in sum Si vous l'implémentez de 3 manières, "utilisez /functions.html#sum)" et "(3) utilisez la formule de somme", et mesurez le temps, ce sera comme suit.
In [1]: %%timeit
...: sum = 0
...: for n in range(1000000):
...: sum += n
87.3 ms ± 492 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [2]: %%timeit
...: sum(range(1000000))
33.3 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [3]: %%timeit
...: ((1000000-1)*1000000)//2
13 ns ± 0.538 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
Dans cet exemple, la formule de somme est rapide, mais vous n'obtenez pas toujours une telle conversion.
Lors de la génération de la liste, "(1) [Notation d'inclusion de liste](https://docs.python.org/ja/3/reference/expressions.html?highlight=%E5%86%85%E5%8C%" Utilisez 85 # displays-for-lists-sets-and-dictionaries) "" (2) append dans une boucle for " "(3) Cache append" "(4) Utiliser la fonction intégrée list" Si vous l'implémentez et mesurez le temps, ce sera comme suit.
In [1]: %%timeit
...: l = [n for n in range(1000000)]
81.7 ms ± 1.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [2]: %%timeit
...: l = []
...: for n in range(1000000):
...: l.append(n)
130 ms ± 2.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [3]: %%timeit
...: l = []
...: l_append = l.append
...: for n in range(1000000):
...: l_append(n)
97.9 ms ± 2.01 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [4]: %%timeit
...: l = list(range(1000000))
58.1 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Dans cet exemple, nous générons juste une liste, mais en général, vous devriez faire un peu de "faire" avant d'ajouter. Etant donné que ce "traitement divers" est souvent plus lent, il est plus efficace d'accélérer le "traitement divers".
Les algorithmes et les structures de données sont des domaines qui ont été étudiés depuis l'Antiquité. Si le montant du calcul est $ O (n) $ et $ O (n ^ 2) $ en notation d'ordre, le temps d'exécution est complètement différent. Vous pouvez implémenter vous-même l'algorithme et la structure de données, ou vous pouvez utiliser les classes de commodité trouvées dans la bibliothèque standard.
Voici quelques exemples d'algorithmes et de structures de données trouvés dans la bibliothèque standard.
Si vous souhaitez déterminer plusieurs fois qu'une liste contient un élément particulier, définissez la liste [set](https://docs.python.org/ja/3/library/stdtypes.html#set- Il est plus rapide de convertir en types-set-frozenset), puis de s'enregistrer. Si vous ne jugez qu'une seule fois, le temps de traitement pour convertir en un ensemble sera effectif.
In [1]: %%timeit
...: l = list(range(10000))
...: for n in range(10000):
...: n in l
1.04 s ± 19 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [2]: %%timeit
...: l = list(range(10000))
...: s = set(l)
...: for n in range(10000):
...: n in s
1.45 ms ± 14.5 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Cette fois, je n'ai cherché que 10 000 fois une liste de 10 000 éléments et j'ai obtenu ce résultat. Avec cette quantité d'exploration, je pense que cela apparaîtra souvent dans des applications réelles.
Vous pouvez utiliser collections.deque si vous souhaitez ajouter plusieurs fois une valeur en haut de la liste.
In [1]: from collections import deque
In [2]: %%timeit
...: l = []
...: for n in range(100000):
...: l.insert(0, n)
6.29 s ± 28.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [3]: %%timeit
...: dq = deque()
...: for n in range(100000):
...: dq.appendleft(n)
11.7 ms ± 93.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Une liste en Python est comme un tableau de longueur variable. Le contenu du tableau est très polyvalent car il n'y a pas d'hypothèses telles que le type ou l'ordre, mais il y a de la place pour une accélération et une efficacité de traitement spécifique. En plus de collections.deque, heapq et [bisect](https://docs.python.org/ja/3/ Les fonctions de gestion des collections (collections de données) telles que library / bisect.html) sont fournies dans la bibliothèque standard.
Le résultat du calcul une fois effectué est stocké dans la mémoire et réutilisé, et les données extraites de la base de données sont au format pickle. Vous pouvez l'accélérer par dump. Tout ce qui renvoie toujours le même résultat si les arguments sont ensemble, comme une fonction mathématique, peut être mis en cache. Dans certains cas, il est plus rapide de lire un fichier local que de communiquer avec la base de données.
Python a une bibliothèque standard définie avec un décorateur appelé functools.lru_cache pour noter la fonction. .. Si vous l'utilisez, le résultat du calcul sera mis en cache une fois, et si les arguments sont identiques, il sera réutilisé.
In [1]: from functools import lru_cache
In [2]: def fib(n):
...: return n if n <= 1 else fib(n-2) + fib(n-1)
In [3]: %%timeit
...: fib(30)
448 ms ± 4.22 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: @lru_cache()
...: def fib(n):
...: return n if n <= 1 else fib(n-2) + fib(n-1)
In [5]: %%timeit -n1 -r1
...: fib(30)
24.2 μs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
@lru_cache C'est facile car il ne vous reste plus qu'à ajouter un décorateur. Vous ne voulez pas vraiment calculer la séquence de Fibonacci, et si vous le souhaitez, vous pourriez avoir une implémentation différente. Est-ce un exemple d'application typique lorsque vous souhaitez calculer l'intégrale par apprentissage automatique?
Bibliothèque standard threading et [multiprocessing](https://docs.python.org/ja/3/library/multiprocessing. Accélérez en utilisant html) ou en parallèle avec concurrent ajouté dans Python 3.2. Utilisez le threading si le processeur n'est pas le goulot d'étranglement et le multitraitement dans le cas contraire. Le thread est plus facile à partager des données, mais GIL (Global Interpreter Lock) ne peut pas surmonter la barrière à 100% du processeur, donc si vous souhaitez utiliser le processeur multicœur, vous devez utiliser le multitraitement.
Il est facile d'utiliser concurrent.future. Avec ProcessPoolExecutor, vous pouvez exécuter en parallèle dans plusieurs processus. Il existe également un [ThreadPoolExecutor] multi-thread (https://docs.python.org/ja/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor).
from time import sleep
from random import uniform
def f(x):
sleep(uniform(0, 1) * 10)
return x
from concurrent.futures import ProcessPoolExecutor, Future, wait
with ProcessPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(f, x) for x in range(10)]
done, not_done = wait(futures)
results = [future.result() for future in futures]
Créez simplement le processus que vous souhaitez paralléliser dans une fonction, spécifiez le nombre de nœuds de calcul et appelez-le. Souvenons-nous-en comme une phrase fixe.
Si vous voulez le rendre multi-processus, vous pouvez faire de votre mieux avec shell au lieu de Python. Le multi-processus peut être réalisé en rendant la fonction f ci-dessus exécutable depuis la ligne de commande et en l'appelant en parallèle depuis le shell.
Par exemple, supposons que le fichier d'entrée soit le suivant (entrée).
input
0
1
2
3
4
5
6
7
8
9
Utilisez la commande de fractionnement pour fractionner de manière appropriée.
$ split -l 1 input
Exécutez le script Python avec le fichier fractionné en entrée.
$ for file in `ls x*`; do python f.py ${file}&; done
Combinez les résultats d'exécution divisés.
$ cat x*.result > result
Vous pouvez tout faire en Python, mais selon la situation, vous pouvez facilement le rendre multi-processus en ajoutant simplement & dans le shell.
Python a beaucoup de bibliothèques standard, et il existe beaucoup plus de bibliothèques tierces. Les bibliothèques tierces sont publiées sur PyPI et peuvent être facilement installées avec la commande pip.
Les calculs numériques peuvent être effectués à grande vitesse en utilisant une bibliothèque spécialisée dans les calculs numériques comme NumPy. Il existe des bibliothèques optimisées pour des calculs spécifiques, pas seulement des calculs numériques. CuPy facilite les calculs GPU à partir de Python à l'aide de CUDA.
NumPy peut accélérer le calcul des matrices et des vecteurs. Non seulement cela peut être plus rapide, mais le code est plus simple et plus facile à comprendre. Ici, chaque élément de la liste composée de 100 000 éléments est ajouté à l'image d'une matrice 100 × 1000.
In [1]: import numpy as np
In [2]: %%timeit
...: X = range(100000)
...: Y = range(100000)
...: Z = [x + y for x, y in zip(X, Y)]
13.7 ms ± 140 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [3]: %%timeit
...: X = np.arange(100000)
...: Y = np.arange(100000)
...: Z = X + Y
416 μs ± 2.33 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Vous pouvez voir que NumPy est extrêmement plus rapide. Les listes prennent en compte les types d'éléments et les méthodes de liste qui changent, mais NumPy a des types fixes et BLAS etc. Vous pouvez accélérer car vous pouvez utiliser la bibliothèque de.
Il peut y avoir une différence de vitesse en fonction de l'implémentation de la bibliothèque. Dans la série Python 2, qui sera bientôt abandonnée, pickle et [cPickle](https: //) Il y avait deux bibliothèques (docs.python.org/ja/2.7/library/pickle.html#module-cPickle). Il y a une différence considérable dans le temps d'exécution selon celui qui est utilisé. Dans la série Python 3, cPickle est maintenant appelé en interne si cPickle est disponible lors de l'importation de pickle au lieu du nom de la bibliothèque _pickle.
In [1]: import pickle
In [2]: import cPickle
In [3]: l = range(1000000)
In [4]: %%timeit
...: with open("pickle.pkl", "wb") as f:
...: pickle.dump(l, f)
1 loops, best of 3: 3.75 s per loop
In [5]: %%timeit
...: with open("cPickle.pkl", "wb") as f:
...: cPickle.dump(l, f)
1 loops, best of 3: 376 ms per loop
Puisqu'il s'agit d'un IPython de type Python 2, l'affichage est différent des autres, mais c'est environ 10 fois le rapport de temps d'exécution.
Les processeurs modernes ont diverses fonctionnalités d'accélération. Il existe une différence de temps d'exécution selon que la fonction d'accélération elle-même est activée ou que la bibliothèque qui peut bien utiliser la fonction d'accélération est appelée. Il y a une explication sur la page suivante.
Si vous avez déjà une bibliothèque Python, vous pouvez simplement l'installer et l'utiliser, mais vous n'avez pas toujours une bibliothèque Python pour ce que vous voulez faire. De plus, il existe de nombreux processus inutiles qui ne peuvent pas être résolus en écrivant en Python. Si vous souhaitez toujours accélérer, vous pouvez utiliser l'une des méthodes suivantes.
Python a la capacité de lire une bibliothèque à partir d'un fichier dll Windows ou d'un fichier Linux so et d'exécuter les fonctions de la bibliothèque ([ctypes](https://docs.python.org/ja/3/library/ctypes. html)) dispose d'une bibliothèque standard. Il peut être utilisé lorsqu'il existe déjà une implémentation qui exécute un traitement lent à grande vitesse lors de l'écriture en Python, mais il n'est pas possible de l'appeler depuis Python.
La bibliothèque math de Python n'a pas de fonction cbrt pour les racines cubiques, mais [libm](http: //www.c) -tipsref.com/reference/math.html). Pour l'appeler, procédez comme suit:
In [1]: from ctypes import cdll, c_double
In [2]: libm = cdll.LoadLibrary("libm.so.6")
In [3]: libm.cbrt.restype = c_double
In [4]: libm.cbrt.argtypes = (c_double,)
In [5]: libm.cbrt(8.0)
Out[5]: 2.0
Je ne suis pas satisfait de cet exemple, mais j'espère que vous comprenez qu'il est facile d'appeler. Le virage du côté Python est lent, vous pouvez donc l'accélérer en appelant une fonction qui exécute également le processus de virage.
Les [Extensions C] de Python (https://docs.python.org/ja/3/extending/extending.html) vous permettent de créer des bibliothèques qui peuvent être appelées depuis Python en utilisant les langages C ou C ++. Python est comme un wrapper pour une bibliothèque faite en C / C ++ en premier lieu, alors créons un côté qui est appelé par le wrapper.
Créons une fonction d'addition avec l'extension C. Tout d'abord, écrivez le code source suivant en langage C.
samplemodule.c
#include <Python.h>
static PyObject * sample_add(PyObject *self, PyObject *args) {
int x, y, z;
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
return NULL;
}
z = x + y;
return PyLong_FromLong(z);
}
static PyObject *SampleError;
static PyMethodDef SampleMethods[] = {
{"add", sample_add, METH_VARARGS, "Sum x and y then return the sum."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef samplemodule = {
PyModuleDef_HEAD_INIT, "sample", NULL, -1, SampleMethods
};
PyMODINIT_FUNC PyInit_sample(void) {
PyObject *m;
m = PyModule_Create(&samplemodule);
if (m == NULL) {
return NULL;
}
SampleError = PyErr_NewException("sample.error", NULL, NULL);
Py_XINCREF(SampleError);
if (PyModule_AddObject(m, "error", SampleError) < 0) {
Py_XDECREF(SampleError);
Py_CLEAR(SampleError);
Py_DECREF(m);
return NULL;
}
return m;
}
Spécifiez le fichier d'en-tête Python et compilez avec gcc.
$ gcc -I`python -c 'from distutils.sysconfig import *; print(get_python_inc())'` -DPIC -shared -fPIC -o sample.so samplemodule.c
Maintenant que sample.so est terminé, démarrez l'interpréteur Python (IPython) et chargez-le.
In [1]: import sample
In [2]: sample.add(1, 2)
Out[2]: 3
Boost.Python vous permet d'implémenter une bibliothèque Python avec moins de code. Vous n'avez pas à vous soucier trop des différences de version de Python. Au lieu de cela, cela dépend de la bibliothèque Boost, donc la bibliothèque Python ne se chargera pas dans un environnement sans la bibliothèque Boost. Une bibliothèque créée à l'aide de Boost.Python est aussi rapide qu'elle est implémentée en C, selon la façon dont elle est implémentée.
Créons une fonction d'addition avec Boost.Python. Tout d'abord, écrivez le code source suivant en langage C ++. C'est tout.
#include <boost/python.hpp>
int add(int x, int y) {
return x + y;
}
BOOST_PYTHON_MODULE(sample) {
using namespace boost::python;
def("add", &add);
}
Spécifiez le fichier d'en-tête Python et Boost.Python et compilez avec g ++.
$ g++ -I`python -c 'from distutils.sysconfig import *; print(get_python_inc())'` -lboost_python -DPIC -shared -fPIC -o sample.so sample.cpp
Maintenant que sample.so est terminé, démarrez l'interpréteur Python (IPython) et chargez-le.
In [1]: import sample
In [2]: sample.add(1, 2)
Out[2]: 3
La quantité de code est beaucoup plus courte que l'écriture à partir de zéro en langage C. C'est bien.
Avec Cython, vous pouvez écrire du code source dans un langage légèrement modifié de Python et générer des bibliothèques aussi vite que celles écrites en C. La procédure consiste à créer un fichier pyx dans un langage de type Python, à générer du code source en langage C et à le compiler.
Créons une fonction en Cython pour calculer la séquence de Fibonacci. Tout d'abord, créez un fichier pyx. Il a presque la même grammaire que Python, avec quelques informations de type.
fib.pyx
def fib(int n):
return n if n <= 1 else fib(n-2) + fib(n-1)
Convertissez le fichier pyx en code source en langage C avec la commande cython. Puis compilez avec gcc.
$ cython fib.pyx
$ gcc -I`python -c 'from distutils.sysconfig import *; print(get_python_inc())'` -DPIC -shared -fPIC -o fib.so fib.c
Maintenant que fib.so est créé, démarrez l'interpréteur Python (IPython) et chargez-le.
In [1]: from fib import fib
In [2]: %%timeit
...: fib(30)
304 ns ± 2.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Quand j'ai tout écrit en Python, c'était "448 ms ± 4,22 ms", ce qui était une amélioration considérable.
Jusqu'à présent, nous avons créé une bibliothèque en langage C pour l'accélérer. La création d'une bibliothèque nécessitait une connaissance des extensions de C et Cython vers Python et des langages d'apprentissage autres que Python. Cependant, apprendre une nouvelle langue peut être une tâche ardue. Ici, nous allons présenter les techniques suivantes qui peuvent étendre les programmes Python existants tels quels.
Numba utilise LLVM pour générer du bytecode à partir de LLRM IR pour la vitesse. Comme vous pouvez le voir dans Ce que vous pouvez faire avant qu'un script Python ne soit exécuté-Qiita, Python est un langage de script de type VM avec des instructions intermédiaires (byte code). Le processus est réalisé en exécutant.
Selon le Guide sur le site d'origine, non seulement le code d'octet est optimisé mais aussi la machine Il semble générer une instruction et remplacer un appel de fonction. C'est incroyable ...
In [1]: @jit
...: def fib(n):
...: return n if n <= 1 else fib(n-2) + fib(n-1)
In [9]: %%timeit
...: fib(30)
12.6 ms ± 187 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
PyPy est un système de traitement Python écrit en Python. ~~ RPython, qui a des fonctions légèrement plus limitées que Python et est facile à optimiser, fonctionne. Il est implémenté dans ~~ RPython et est compatible avec Python 2.7 et Python 3.6. Selon Compatibilité Python --PyPy, Django et Flask Des bibliothèques bien connues telles que /) ont été confirmées pour fonctionner, et selon PyPy Speed, la vitesse d'exécution semble assez excellente.
Dans cet article, j'ai présenté les méthodes d'accélération suivantes.
La méthode appropriée pour traiter un processus qui vous semble lent dépend du moment et du cas. Ce serait bien de pouvoir appliquer la méthode optimale après avoir envisagé diverses options.
Je n'ai donné que des exemples de séquences de Fibonacci, mais trouver des séquences de Fibonatch n'est pas une tâche courante pour les applications. Cython peut être plus rapide que Numba en fonction du traitement que vous souhaitez accélérer. Cette fois, j'utilise la récurrence pour trouver la séquence de Fibonacci, mais si vous supprimez la récurrence en premier lieu, ce sera plus rapide ...
Que votre qualité de vie Python soit merveilleuse.
En écrivant cet article, je me suis référé à l'article suivant.
De plus, j'ai été redevable de merveilleux articles et livres autres que ceux ci-dessus lors de l'apprentissage de Python. Je suis vraiment désolé de ne pouvoir le présenter ici. Merci d'avoir publié et publié d'excellentes informations. Je crois que de bonnes informations seront utiles à ceux qui apprennent Python.
Recommended Posts