Je pense qu'il existe de nombreuses façons d'accélérer Python.
Il peut y avoir d'autres raisons, mais la raison pour laquelle C est une API.
Surtout pour le dernier élément, les outils de configuration récents ont évolué et il est devenu plus facile à construire et à piloter, il semble donc y avoir de la place pour reconsidérer l'API.
Je pense qu'il y a une demande dans des créneaux, comme lorsqu'une personne comme l'auteur qui ne peut pas du tout écrire en C veut utiliser efficacement un programme C à petite échelle donné par un expert.
En guise d'exemple, utilisons un algorithme appelé [Eratostenes Sieve](https://ja.wikipedia.org/wiki/Eratostenes Sieve). Je ne connais pas les détails, veuillez donc vous référer à la destination du lien et rechercher les nombres premiers inférieurs ou égaux à l'entier spécifié avec l'un des algorithmes de jugement des nombres premiers. Par exemple, si l'entier donné est 10, [2, 3, 5, 7] sera recherché.
Pour le code C, je me suis référé à ici.
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#define MAX 1000000
double gettimeofday_sec()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + (double)tv.tv_usec*1e-6;
}
double prim(int n)
{
double start,end;
start = gettimeofday_sec();
int i, j;
int p[n];
for(i=0 ; i<n ; i++) p[i] = 0;
p[0] = 1;
for(i=2 ; i<=n/2 ; i++){
for(j=2 ; i*j<=n ; j++){
if(p[i*j-1] == 0)
p[i*j-1] = 1;
}
}
end = gettimeofday_sec();
return end - start;
}
Comme il est trop difficile de sortir tous les résultats de la recherche, j'en ai fait une fonction qui renvoie le temps de traitement.
virtualenv
Il est fortement recommandé de créer un environnement virtuel à l'avance.
Cette fois, nous partirons du principe que «virtualenv wrapper» est installé.
Le nom de l'environnement virtuel est ʻapi_test. Spécifiez la version de python que vous souhaitez construire dans l'argument de mot-clé
--python =de
mkvirtualenv. Cela a un effet important sur la construction du package (création de la roue) décrite plus loin, spécifiez donc le Python sur lequel le package sera distribué. Dans mon cas, le chemin est
/ usr / bin / python3.5`, mais veuillez l'ajuster à votre propre environnement.
mkvirtualenv api_test --python=/usr/bin/python3.5
Créez un répertoire arbitraire et la configuration finale sera la suivante.
.
├── api_sample
│ ├── __init__.py
│ └── py
│ └── __init__.py
├── prim.c
└── setup.py
Si vous n'êtes pas familiarisé avec la création de fichiers et de répertoires, vous pouvez également cloner à partir de mon référentiel github.
git clone https://github.com/drillan/python3_c_api_sample.git
Créez et éditez prim.c
directement sous le répertoire.
Incorporez l'API Python.
#include <Python.h>
Tous les symboles visibles par l'utilisateur définis dans «Python.h» semblent avoir le préfixe «Py» ou «PY».
Certains systèmes ont des définitions de préprocesseur qui affectent la définition des en-têtes standard, il semble donc que Python.h
doit être inclus avant tout en-tête standard.
Cette fois, nous traiterons la fonction prim ()
de l'exemple de code C ci-dessus comme un module Python.
static PyObject *
prim(PyObject *self, PyObject *args)
{
int n;
if(!PyArg_ParseTuple(args, "i", &n))
return NULL;
double start,end;
start = gettimeofday_sec();
int i, j;
int p[n];
for(i=0 ; i<n ; i++) p[i] = 0;
p[0] = 1;
for(i=2 ; i<=n/2 ; i++){
for(j=2 ; i*j<=n ; j++){
if(p[i*j-1] == 0)
p[i*j-1] = 1;
}
}
end = gettimeofday_sec();
return Py_BuildValue("d", end - start);
}
L'argument self
est passé au module s'il s'agit d'une fonction au niveau du module, et la méthode reçoit une instance d'objet.
L'argument ʻargsdevient un pointeur vers l'objet taple Python qui contient l'argument. Chaque élément du taple correspond à chaque argument de la liste d'arguments au moment de l'appel. Ce type de manipulation est nécessaire car les arguments sont donnés par Python. J'utilise également
PyArg_ParseTuple ()car l'argument donné doit être converti en type C. Le deuxième argument, «i», fait référence au type int, «d» est le type double et «s» est le type char. Enfin,
Py_BuildValue ()` retourne au type qui peut à nouveau être reçu par Python.
De cette façon, afin de convertir une fonction C en une API Python, il est nécessaire d'être conscient du flux de Python-> C-> Python.
Enregistrez la fonction prim ()
créée ci-dessus dans la table des méthodes afin qu'elle puisse être appelée en tant que méthode Python.
static PyMethodDef methods[] = {
{"prim", prim, METH_VARARGS},
{NULL, NULL}
};
Les trois entrées, à leur tour, sont le nom de la méthode, un pointeur vers l'implémentation C et un bit indicateur indiquant comment effectuer l'appel.
METH_VARARGS
est une convention d'appel généralement utilisée dans les méthodes de type PyCFunction, où les arguments de la fonction sont donnés au format tuple.
Si vous voulez donner des arguments de mot-clé, spécifiez METH_KEYWORDS
.
docstring
Normalement, il semble être généré en utilisant PyDoc_STRVAR ()
.
Il peut être enregistré en tant que docstring lors de l'enregistrement d'un module décrit plus loin.
PyDoc_STRVAR(api_doc, "Python3 API sample.\n");
Enregistrez le nom du module, etc. afin qu'il puisse être importé depuis Python. Spécifiez le nom du module, la docstring, la zone de mémoire du module et la méthode.
static struct PyModuleDef cmodule = {
PyModuleDef_HEAD_INIT,
"c", /* name of module */
api_doc, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
methods
};
Cette fois, le nom du module est «c». Spécifier le troisième «-1» semble signifier que le module ne prend pas en charge les sous-interprètes en raison de son état global. Je ne me connais pas, donc si vous voulez en savoir plus, veuillez vous référer à PEP 3121.
La fonction PyInit_c ()
est appelée lorsque le module c
est importé.
La fonction PyModule_Create ()
crée le module défini ci-dessus et le renvoie à la fonction d'initialisation. (Quelque chose comme «__init __. Py»?)
Notez que la fonction Pyinit_name ()
a des noms différents selon le nom du module.
prim.c
#include <Python.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#define MAX 1000000
double gettimeofday_sec()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + (double)tv.tv_usec*1e-6;
}
static PyObject *
prim(PyObject *self, PyObject *args)
{
int n;
if(!PyArg_ParseTuple(args, "i", &n))
return NULL;
double start,end;
start = gettimeofday_sec();
int i, j;
int p[n];
for(i=0 ; i<n ; i++) p[i] = 0;
p[0] = 1;
for(i=2 ; i<=n/2 ; i++){
for(j=2 ; i*j<=n ; j++){
if(p[i*j-1] == 0)
p[i*j-1] = 1;
}
}
end = gettimeofday_sec();
return Py_BuildValue("d", end - start);
}
static PyMethodDef methods[] = {
{"prim", prim, METH_VARARGS},
{NULL, NULL}
};
PyDoc_STRVAR(api_doc, "Python3 API sample.\n");
static struct PyModuleDef cmodule = {
PyModuleDef_HEAD_INIT,
"c", /* name of module */
api_doc, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
methods
};
PyInit_c(void)
{
return PyModule_Create(&cmodule);
}
__init __. Py
Vous pouvez le construire tel quel et l'importer, mais soyez conscient du paquet et enregistrez-le dans ʻapi_sample / __ init __. Py`.
api_sample/__init__.py
import c
Comme c'est un gros problème, je vais écrire le même code en Python et comparer les vitesses.
Écrivez le code équivalent à prim.c
dans ʻapi_sample / py / __ init __. Py` et enregistrez-le.
Veuillez noter que le nom du fichier est le même que ci-dessus, mais la hiérarchie est différente.
api_sample/py/__init__.py
import time
MaxNum = 1000000
def prim(n):
start = time.time()
prime_box = [0 for i in range(n)]
prime_box[0], prime_box[1] = 1, 1
for i in range(n)[2:]:
j = 1
while i * (j + 1) < n:
prime_box[i * (j + 1)] = 1
j += 1
end = time.time()
return end - start
if __name__ == '__main__':
print(prim(MaxNum))
C'est finalement une construction, mais il semble que setuptools
le construira simplement en l'écrivant dans setup.py
. C'est un moment opportun.
Alors créez setup.py
.
setup.py
from setuptools import setup, Extension
c = Extension('c', sources=['prim.c'])
setup(name="api_sample", version="0.0.0",
description="Python3 API Sample",
packages=['api_sample'], ext_modules=[c])
Est-ce le point qu'il est construit comme une extension de Python en spécifiant l'emplacement du code source C avec setuptools.Extension ()
?
En spécifiant le nom du module spécifié ci-dessus dans l'argument mot-clé ʻext_modules de
setuptools.setup () `, il peut être appelé comme un module Python.
python setup.py build
Faire ce qui précède va construire les modules requis et les enregistrer dans le répertoire build
.
Installons-le et utilisons-le immédiatement.
python setup.py install
pip freeze
Si vous pouvez l'installer sans aucun problème, la sortie sera la suivante.
api-sample==0.0.0
Commençons par le code Python.
python -c "from api_sample import py; print(py.prim(1000000))"
C'était à propos de ça dans mon environnement
5.6855926513671875
Puis le code C.
python -c "from api_sample import c; print(c.prim(1000000))"
0.0776968002319336
C'est beaucoup plus rapide!
Créez une roue.
python setup.py bdist_wheel
Dans mon environnement, un fichier appelé ʻapi_sample-0.0.0-cp35-cp35m-linux_x86_64.whl` a été créé.
Lorsque vous exécutez ce qui précède, un fichier de roue adapté à l'environnement d'exécution sera créé dans le répertoire dist
.
C'est la raison principale pour laquelle nous recommandons virtualenv, et il est très pratique à distribuer car en commutant l'environnement virtuel et en recréant la roue, un package adapté à l'environnement est créé.
L'environnement Windows est souvent le goulot d'étranglement pour les packages à créer.
Normalement, Visual Studio est requis, mais vous pouvez le générer en installant Visual C ++ Build Tools.
En exécutant python setup.py bdist_wheel
avec ceci installé, la roue pour Windows sera construite et pourra être distribuée aux utilisateurs qui n'ont pas d'environnement de construction.
De plus, en installant Python 32 bits et 64 bits et en intégrant chaque environnement virtualenv, vous pouvez créer une roue qui prend en charge à la fois 32 bits / 64 bits.
Si vous enregistrez la roue pour chaque plate-forme dans PyPI, les utilisateurs peuvent facilement l'installer avec pip install
.
Recommended Posts