Heim  >  Artikel  >  Backend-Entwicklung  >  Effizienter Python-Code

Effizienter Python-Code

巴扎黑
巴扎黑Original
2017-04-05 15:45:551466Durchsuche

Meiner Meinung nach ist die Python-Community in drei Schulen unterteilt, nämlich die Python 2.x-Organisation, die 3.x-Organisation und die PyPy-Organisation. Diese Klassifizierung läuft im Wesentlichen auf die Kompatibilität und Geschwindigkeit der Bibliothek hinaus. Dieser Artikel konzentriert sich auf einige gängige Techniken zur Codeoptimierung und die erhebliche Leistungsverbesserung nach dem Kompilieren in C. Natürlich werde ich auch die Laufzeit der drei wichtigsten Python-Genres angeben. Mein Ziel ist es nicht, zu beweisen, dass eines besser ist als das andere, sondern Ihnen nur eine Vorstellung davon zu geben, wie Sie diese spezifischen Beispiele in verschiedenen Kontexten vergleichen können.

Verwendung des Generators

Eine häufig übersehene Speicheroptimierung ist die Verwendung von Generatoren. Mit Generatoren können wir eine Funktion erstellen, die jeweils nur einen Datensatz zurückgibt, anstatt alle Datensätze auf einmal. Wenn Sie python2.x verwenden, verwenden Sie deshalb xrange anstelle von range oder ifilter anstelle von filter. Ein gutes Beispiel ist das Erstellen einer großen Liste und das Zusammenfügen dieser Listen.

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

Das geht nicht nur etwas schneller, es verhindert auch, dass Sie die gesamte Liste im Speicher speichern müssen

Einführung in Ctypes

Für wichtige Leistungscodes stellt uns Python selbst auch eine API zum Aufrufen von C-Methoden zur Verfügung, hauptsächlich über ctypes. Sie können ctypes verwenden, ohne C-Code zu schreiben. Standardmäßig stellt Python eine vorkompilierte Standard-C-Bibliothek bereit. Kehren wir zum Generatorbeispiel zurück und sehen, wie viel Zeit es dauert, es mithilfe von Ctypes zu implementieren.

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

Ersetzte es einfach durch eine Zufallsfunktion von c und die Laufzeit wurde um mehr als die Hälfte reduziert! Wenn ich Ihnen jetzt sage, dass wir es besser machen können, würden Sie es glauben?

Einführung in Cython

Cython ist eine Obermenge von Python, die es uns ermöglicht, C-Funktionen aufzurufen und Variablen zu deklarieren, um die Leistung zu verbessern. Bevor wir versuchen, es zu verwenden, müssen wir Cython installieren.

sudo pip install cython

Cython ist im Wesentlichen ein Zweig einer anderen Pyrex-ähnlichen Bibliothek, die sich nicht mehr in der Entwicklung befindet. Es kompiliert unseren Python-ähnlichen Code in eine C-Bibliothek, die wir in einer Python-Datei aufrufen können. Verwenden Sie das Suffix .pyx anstelle des Suffixes .py für Ihre Python-Dateien. Sehen wir uns an, wie Sie unseren Generatorcode mit Cython ausführen.

#cython_generator.pyx
import random

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

Wir müssen eine setup.py erstellen, damit wir Cython dazu bringen können, unsere Funktion zu kompilieren.

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

Kompilieren mit:

python setup.py build_ext --inplace

Sie sollten zwei Dateien sehen können: die Datei cython_generator.c und die Datei generator.so. Wir verwenden die folgende Methode, um unser Programm zu testen:

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

Nicht schlecht, mal sehen, ob es etwas gibt, das wir verbessern können. Wir können „num“ zunächst als Ganzzahl deklarieren und dann die Standard-C-Bibliothek importieren, die für unsere Zufallsfunktion verantwortlich ist.

#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

Wenn wir kompilieren und erneut ausführen, werden wir diese erstaunliche Zahl sehen.

>>> 0.033586025238

Nur ein paar Änderungen brachten ordentliche Ergebnisse. Manchmal ist diese Änderung jedoch mühsam. Sehen wir uns also an, wie man sie mit normalem Python durchführt.

Einführung in PyPy PyPy ist ein Just-in-Time-Compiler für Python 2.7.3. Laienhaft ausgedrückt bedeutet dies, dass Ihr Code schneller ausgeführt wird. Quora verwendet PyPy in der Produktion. Auf der Download-Seite von PyPy gibt es einige Installationsanweisungen. Wenn Sie jedoch Ubuntu verwenden, können Sie es über apt-get installieren. Die Funktionsweise ist sofort einsatzbereit, also kein verrücktes Bashen oder Ausführen von Skripten, einfach herunterladen und ausführen. Sehen wir uns an, wie sich unser ursprünglicher Generatorcode unter PyPy verhält.

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

Wow! Ohne Änderung einer einzigen Codezeile ist die Laufgeschwindigkeit achtmal schneller als bei der reinen Python-Implementierung.

Weitere Tests Warum weitere Forschung? PyPy ist der Champion! Nicht ganz wahr. Obwohl die meisten Programme auf PyPy laufen können, werden einige Bibliotheken nicht vollständig unterstützt. Darüber hinaus ist es einfacher, C-Erweiterungen für Ihr Projekt zu schreiben, als den Compiler zu ändern. Lassen Sie uns etwas tiefer gehen und sehen, wie ctypes es uns ermöglicht, Bibliotheken in C zu schreiben. Testen wir die Geschwindigkeit der Zusammenführungssortierung und der Berechnung der Fibonacci-Folge. Das Folgende ist der C-Code (functions.c), den wir verwenden werden:

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

Auf der Linux-Plattform können wir es mit der folgenden Methode in eine gemeinsam genutzte Bibliothek kompilieren:

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

Mit ctypes können Sie diese Bibliothek verwenden, indem Sie die gemeinsam genutzte Bibliothek „libfunctions.so“ laden, genau wie wir es zuvor mit der Standard-C-Bibliothek getan haben. Hier vergleichen wir die Python-Implementierung und die C-Implementierung. Jetzt beginnen wir mit der Berechnung der Fibonacci-Folge:

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

Wie erwartet ist C schneller als Python und PyPy. Auf die gleiche Weise können wir auch Zusammenführungssortierungen vergleichen.

Wir haben uns noch nicht mit der Cypes-Bibliothek befasst, daher spiegeln diese Beispiele nicht die leistungsstarke Seite von Python wider. Die Cypes-Bibliothek weist nur wenige Standardtypbeschränkungen auf, wie z. B. int, char array, float, bytes usw. Standardmäßig gibt es kein ganzzahliges Array. Durch Multiplikation mit c_int (ctype ist der Typ int) können wir jedoch indirekt ein solches Array erhalten. Dies wird auch in Zeile 7 des Codes angezeigt. Wir haben ein c_int-Array erstellt, ein Array unserer Zahlen, und sie in ein c_int-Array

gepackt

  主要的是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

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

Effizienter Python-Code

  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

Das obige ist der detaillierte Inhalt vonEffizienter Python-Code. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn