Maison  >  Article  >  développement back-end  >  Code Python efficace

Code Python efficace

巴扎黑
巴扎黑original
2017-04-05 15:45:551402parcourir

À mon avis, la communauté python est divisée en trois écoles, à savoir l'organisation python 2.x, l'organisation 3.x et l'organisation PyPy. Cette classification se résume essentiellement à la compatibilité et à la vitesse des bibliothèques. Cet article se concentrera sur certaines techniques courantes d'optimisation du code et sur l'amélioration significative des performances après la compilation en C. Bien sûr, je donnerai également la durée d'exécution des trois principaux genres Python. Mon objectif n'est pas de prouver que l'un est meilleur que l'autre, juste de vous donner une idée de comment comparer en utilisant ces exemples spécifiques dans différents contextes.

Utilisation du générateur

Une optimisation de la mémoire souvent négligée est l'utilisation de générateurs. Les générateurs nous permettent de créer une fonction qui renvoie un seul enregistrement à la fois au lieu de tous les enregistrements à la fois. Si vous utilisez python2.x, c'est pourquoi vous utilisez xrange au lieu de range ou ifilter au lieu de filter. Un bon exemple consiste à créer une grande liste et à les assembler.

import timeit
import random

def generate(num):
while num:
yield random.randrange(10)
num -= 1

def create_list(num):
numbers = []
while num:
numbers.append(random.randrange(10))
num -= 1
return numbers
print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))
>>> 0.88098192215 #Python 2.7
>>> 1.416813850402832 #Python 3.2
print(timeit.timeit("sum(create_list(999))", setup="from __main__ import create_list", number=1000))
>>> 0.924163103104 #Python 2.7
>>> 1.5026731491088867 #Python 3.2

Non seulement c'est un peu plus rapide, mais cela vous évite également de stocker toute la liste en mémoire

! Introduction aux Ctypes

Pour les codes de performances clés, python lui-même nous fournit également une API pour appeler des méthodes C, principalement via des ctypes. Vous pouvez utiliser des ctypes sans écrire de code C. Par défaut, python fournit une bibliothèque c standard précompilée. Revenons à l'exemple du générateur et voyons combien de temps il faut pour l'implémenter à l'aide de ctypes.

import timeit
from ctypes import cdll

def generate_c(num):
#Load standard C library
libc = cdll.LoadLibrary("libc.so.6") #Linux
#libc = cdll.msvcrt #Windows
while num:
yield libc.rand() % 10
num -= 1

print(timeit.timeit("sum(generate_c(999))", setup="from __main__ import generate_c", number=1000))
>>> 0.434374809265 #Python 2.7
>>> 0.7084300518035889 #Python 3.2

Je viens de le remplacer par une fonction aléatoire de c, et le temps d'exécution a été réduit de plus de moitié ! Maintenant, si je vous dis que nous pouvons faire mieux, le croiriez-vous ?

Introduction à Cython

Cython est un sur-ensemble de Python qui nous permet d'appeler des fonctions C et de déclarer des variables pour améliorer les performances. Avant d'essayer de l'utiliser, nous devons installer Cython.

sudo pip install cython

Cython est essentiellement un fork d'une autre bibliothèque de type Pyrex qui n'est plus en développement. Il compile notre code de type Python dans une bibliothèque C que nous pouvons appeler dans un fichier python. Utilisez le suffixe .pyx au lieu du suffixe .py pour vos fichiers Python. Voyons comment exécuter notre code générateur à l'aide de Cython.

#cython_generator.pyx
import random

def generate(num):
while num:
yield random.randrange(10)
num -= 1

Nous devons créer un setup.py pour que Cython puisse compiler notre fonction.

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [Extension("generator", ["cython_generator.pyx"])]
)

Compiler en utilisant :

python setup.py build_ext --inplace

Vous devriez pouvoir voir deux fichiers : le fichier cython_generator.c et le fichier generator.so. Nous utilisons la méthode suivante pour tester notre programme :

import timeit
print(timeit.timeit("sum(generator.generate(999))", setup="import generator", number=1000))
>>> 0.835658073425

. Pas mal, voyons s'il y a quelque chose que nous pouvons améliorer. Nous pouvons d'abord déclarer "num" comme un entier, puis importer la bibliothèque C standard pour être responsable de notre fonction aléatoire.

#cython_generator.pyx
cdef extern from "stdlib.h":
int c_libc_rand "rand"()

def generate(int num):
while num:
yield c_libc_rand() % 10
num -= 1

Si nous compilons et réexécutons, nous verrons ce nombre incroyable.

>>> 0.033586025238

Quelques changements seulement ont donné des résultats décents. Cependant, ce changement est parfois fastidieux, voyons donc comment le faire en utilisant Python standard.

Introduction à PyPy PyPy est un compilateur juste à temps pour Python 2.7.3 En termes simples, cela signifie rendre votre code plus rapide. Quora utilise PyPy en production. PyPy propose des instructions d'installation sur sa page de téléchargement, mais si vous utilisez Ubuntu, vous pouvez l'installer via apt-get. La façon dont cela fonctionne est prête à l'emploi, donc pas de bash fou ni d'exécution de scripts, il suffit de télécharger et d'exécuter. Voyons comment notre code générateur d'origine fonctionne sous PyPy.

import timeit
import random

def generate(num):
while num:
yield random.randrange(10)
num -= 1

def create_list(num):
numbers = []
while num:
numbers.append(random.randrange(10))
num -= 1
return numbers
print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))
>>> 0.115154981613 #PyPy 1.9
>>> 0.118431091309 #PyPy 2.0b1
print(timeit.timeit("sum(create_list(999))", setup="from __main__ import create_list", number=1000))
>>> 0.140175104141 #PyPy 1.9
>>> 0.140514850616 #PyPy 2.0b1

Ouah! Sans modifier une seule ligne de code, la vitesse d'exécution est 8 fois plus rapide que l'implémentation Python pure.

Des tests plus approfondis Pourquoi des recherches plus approfondies ? PyPy est le champion ! Pas tout à fait vrai. Bien que la plupart des programmes puissent fonctionner sur PyPy, certaines bibliothèques ne sont pas entièrement prises en charge. De plus, il est plus facile d’écrire des extensions C pour votre projet que de changer de compilateur. Creusons un peu plus et voyons comment les ctypes nous permettent d'écrire des bibliothèques en C. Testons la vitesse du tri par fusion et du calcul de la séquence de Fibonacci. Voici le code C (functions.c) que nous utiliserons :

/* functions.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* http://rosettacode.org/wiki/Sorting_algorithms/Merge_sort#C */
inline void
merge (int *left, int l_len, int *right, int r_len, int *out)
{
int i, j, k;
for (i = j = k = 0; i < l_len && j < r_len;)
out[k++] = left[i] < right[j] ? left[i++] : right[j++];
while (i < l_len)
out[k++] = left[i++];
while (j < r_len)
out[k++] = right[j++];
}

/* inner recursion of merge sort */
void
recur (int *buf, int *tmp, int len)
{
int l = len / 2;
if (len <= 1)
return;
/* note that buf and tmp are swapped */
recur (tmp, buf, l);
recur (tmp + l, buf + l, len - l);
merge (tmp, l, tmp + l, len - l, buf);
}

/* preparation work before recursion */
void
merge_sort (int *buf, int len)
{
/* call alloc, copy and free only once */
int *tmp = malloc (sizeof (int) * len);
memcpy (tmp, buf, sizeof (int) * len);
recur (buf, tmp, len);
free (tmp);
}

int
fibRec (int n)
{
if (n < 2)
return n;
else
return fibRec (n - 1) + fibRec (n - 2);
}

Sur la plateforme Linux, nous pouvons le compiler dans une bibliothèque partagée en utilisant la méthode suivante :

gcc -Wall -fPIC -c functions.c
gcc -shared -o libfunctions.so functions.o

En utilisant les ctypes, vous pouvez utiliser cette bibliothèque en chargeant la bibliothèque partagée "libfunctions.so", tout comme nous l'avons fait avec la bibliothèque C standard plus tôt. Ici, nous allons comparer l'implémentation Python et l'implémentation C. Commençons maintenant à calculer la suite de Fibonacci :

# functions.py

from ctypes import *
import time

libfunctions = cdll.LoadLibrary("./libfunctions.so")

def fibRec(n):
if n < 2:
return n
else:
return fibRec(n-1) + fibRec(n-2)

start = time.time()
fibRec(32)
finish = time.time()
print("Python: " + str(finish - start))

# C Fibonacci
start = time.time()
x = libfunctions.fibRec(32)
finish = time.time()
print("C: " + str(finish - start))

Comme nous nous y attendions, C est plus rapide que Python et PyPy. Nous pouvons également comparer les tris par fusion de la même manière.

Nous n'avons pas encore exploré la bibliothèque Cypes, donc ces exemples ne reflètent pas le côté puissant de Python. La bibliothèque Cypes n'a que quelques restrictions de type standard, telles que int, char array, float, bytes, etc. Par défaut, il n'y a pas de tableau d'entiers, cependant en multipliant par c_int (ctype est de type int) on peut obtenir un tel tableau indirectement. C’est également ce que montre la ligne 7 du code. Nous avons créé un tableau c_int, un tableau de nos nombres et les avons emballés dans un tableau c_int

  主要的是c语言不能这样做,而且你也不想。我们用指针来修改函数体。为了通过我们的c_numbers的数列,我们必须通过引用传递merge_sort功能。运行merge_sort后,我们利用c_numbers数组进行排序,我已经把下面的代码加到我的functions.py文件中了。

#Python Merge Sort
from random import shuffle, sample

#Generate 9999 random numbers between 0 and 100000
numbers = sample(range(100000), 9999)
shuffle(numbers)
c_numbers = (c_int * len(numbers))(*numbers)

from heapq import merge
def merge_sort(m):
if len(m) <= 1:
return m
middle = len(m) // 2
left = m[:middle]
right = m[middle:]
left = merge_sort(left)
right = merge_sort(right)
return list(merge(left, right))

start = time.time()
numbers = merge_sort(numbers)
finish = time.time()
print("Python: " + str(finish - start))

#C Merge Sort
start = time.time()
libfunctions.merge_sort(byref(c_numbers), len(numbers))
finish = time.time()
print("C: " + str(finish - start))
Python: 0.190635919571 #Python 2.7
Python: 0.11785483360290527 #Python 3.2
Python: 0.266992092133 #PyPy 1.9
Python: 0.265724897385 #PyPy 2.0b1
C: 0.00201296806335 #Python 2.7 + ctypes
C: 0.0019741058349609375 #Python 3.2 + ctypes
C: 0.0029308795929 #PyPy 1.9 + ctypes
C: 0.00287103652954 #PyPy 2.0b1 + ctypes

  这儿通过表格和图标来比较不同的结果。

Code Python efficace

  Merge Sort Fibonacci
Python 2.7 0.191 1.187
Python 2.7 + ctypes 0.002 0.044
Python 3.2 0.118 1.272
Python 3.2 + ctypes 0.002 0.046
PyPy 1.9 0.267 0.564
PyPy 1.9 + ctypes 0.003 0.048
PyPy 2.0b1 0.266 0.567
PyPy 2.0b1 + ctypes 0.003 0.046

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