API C en Python 3

Pourquoi l'API Imasara?

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.

Matière

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.

Préparation

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 =demkvirtualenv. 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

Structure du répertoire

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

Convertir le code C en API Python

Créez et éditez prim.c directement sous le répertoire.

entête

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.

Transformer des fonctions C en objets Python

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 égalementPyArg_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.

Enregistrement de la table de méthode

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");

Enregistrement du module

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.

Fonction d'initialisation

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.

Contenu final prim.c

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);
}

Enregistré dans __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

Bonus, écriture de code Python

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))

Construire

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.

Installation

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

Courir

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!

Distribution

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éé.

Environnement Windows

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.

Lien de référence

Recommended Posts

API C en Python 3
API Evernote en Python
Next Python en langage C
Étendre python en C ++ (Boost.NumPy)
Hit API de Mastodon en Python
Recherche binaire en Python / C ++
API Blender Python dans Houdini (Python 3)
Obtenir l'API arXiv en Python
Créer Awaitable avec l'API Python / C
Frappez l'API Sesami en Python
ABC166 en Python A ~ C problème
Créez Gmail en Python sans utiliser l'API
Résoudre ABC036 A ~ C avec Python
Accédez à l'API Web en Python
Comment envelopper C en Python
Implémentez rapidement l'API REST en Python
Résoudre ABC037 A ~ C avec Python
Accéder à l'API Twitter avec Python
Ecrire un test unitaire de langage C en Python
Python en optimisation
CURL en Python
Métaprogrammation avec Python
Python 3.3 avec Anaconda
Géocodage en python
Méta-analyse en Python
Unittest en Python
Époque en Python
Discord en Python
Allemand en Python
DCI en Python
tri rapide en python
nCr en python
N-Gram en Python
Programmation avec Python
notes de python C ++
Plink en Python
Constante en Python
python, openFrameworks (c ++)
FizzBuzz en Python
Étape AIC en Python
CSV en Python
Assemblage inversé avec Python
Réflexion en Python
Constante en Python
nCr en Python.
format en python
Scons en Python 3
Puyopuyo en python
python dans virtualenv
PPAP en Python
Quad-tree en Python
Réflexion en Python
Chimie avec Python
Hashable en Python
DirectLiNGAM en Python
LiNGAM en Python
Aplatir en Python
Aplatir en python
Fonctionnement de la souris à l'aide de l'API Windows en Python