Heim >Backend-Entwicklung >Python-Tutorial >20 Tipps, um Ihre Python zum Fliegen zu bringen

20 Tipps, um Ihre Python zum Fliegen zu bringen

高洛峰
高洛峰Original
2017-02-24 15:34:321375Durchsuche

Der Artikel, den ich heute geteilt habe, enthält nicht viel Text, sondern hauptsächlich Code. Absolut informativ und unkompliziert, gibt es hauptsächlich 20 Tipps zur Verbesserung der Python-Leistung und zeigt Ihnen, wie Sie sich von langsamem Python verabschieden können. Der ursprüngliche Autor Kaiyuan, ein Full-Stack-Programmierer, verwendet Python, Java, PHP und C++.

1. Zeitkomplexität des Optimierungsalgorithmus

Die Zeitkomplexität des Algorithmus hat den größten Einfluss auf die Ausführungseffizienz des Programms Wählen Sie eine geeignete Datenstruktur, um die zeitliche Komplexität zu optimieren. Beispielsweise beträgt die zeitliche Komplexität der Suche nach einem bestimmten Element in einer Liste und einer Menge O(n) bzw. O(1). Verschiedene Szenarien haben unterschiedliche Optimierungsmethoden. Im Allgemeinen gibt es Ideen wie „Divide and Conquer“, „Branch and Bound“, „Greed“ und „Dynamic Programming“.

2. Reduzieren Sie redundante Daten

Verwenden Sie beispielsweise das obere Dreieck oder das untere Dreieck, um eine große symmetrische Matrix zu speichern. Verwenden Sie die dünn besetzte Matrixdarstellung in Matrizen, in denen 0 Elemente die Mehrheit ausmachen.

3. Richtige Verwendung von copy und deepcopy

Bei Objekten mit Datenstrukturen wie dict und list erfolgt die direkte Zuweisung durch Referenz. In einigen Fällen müssen Sie das gesamte Objekt kopieren. In diesem Fall können Sie copy und deepcopy im Kopierpaket verwenden. Der Unterschied zwischen diesen beiden Funktionen besteht darin, dass letztere rekursiv kopiert. Auch die Effizienz ist unterschiedlich: (Das folgende Programm wird in ipython ausgeführt)

import copy
a = range(100000)
%timeit -n 10 copy.copy(a) # 运行10次 copy.copy(a)

%timeit -n 10 copy.deepcopy(a)
10 loops, best of 3: 1.55 ms per loop
10 loops, best of 3: 151 ms per loop

Das -n nach timeit gibt die Anzahl der Läufe und die letzte an zwei Zeilen entsprechen Die Ausgabe der beiden Zeitenist unten gleich. Man erkennt, dass Letzteres um eine Größenordnung langsamer ist.

4. Verwenden Sie dict oder set, um Elemente zu finden

Python dict und set werden mithilfe von Hash-Tabellen implementiert (ähnlich wie unordered_map in der C++11-Standardbibliothek). Suche Die zeitliche Komplexität von Elementen beträgt O(1).

a = range(1000)
s = set(a)
d = dict((i,1) for i in a)
%timeit -n 10000 100 in d
%timeit -n 10000 100 in s
10000 loops, best of 3: 43.5 ns per loop
10000 loops, best of 3: 49.6 ns per loop

dict ist etwas effizienter (und nimmt mehr Platz ein).

5. Angemessener Einsatz von Generatoren und Ertrag

%timeit -n 100 a = (i for i in range(100000))
%timeit -n 100 b = [i for i in range(100000)]
100 loops, best of 3: 1.54 ms per loop
100 loops, best of 3: 4.56 ms per loop

Was Sie durch die Verwendung von () erhalten, ist das Der für ein Generatorobjekt erforderliche Speicherplatz hat nichts mit der Größe der Liste zu tun, sodass die Effizienz höher ist. In bestimmten Anwendungen ist beispielsweise set(i for i in range(100000)) schneller als set([i for i in range(100000)]).

Aber für Situationen, in denen eine Schleifendurchquerung erforderlich ist:

%timeit -n 10 for x in (i for i in range(100000)): pass
%timeit -n 10 for x in [i for i in range(100000)]: pass

10 loops, best of 3: 6.51 ms per loop
10 loops, best of 3: 5.54 ms per loop

Letzteres ist effizienter, aber wenn es eine Unterbrechung gibt Schleife, Die Vorteile der Verwendung von Generatoren liegen auf der Hand. Yield wird auch zum Erstellen von Generatoren verwendet:

def yield_func(ls):
  for i in ls:
    yield i+1

def not_yield_func(ls):
  return [i+1 for i in ls]

ls = range(1000000)
%timeit -n 10 for i in yield_func(ls):pass

%timeit -n 10 for i in not_yield_func(ls):pass

10 loops, best of 3: 63.8 ms per loop
10 loops, best of 3: 62.9 ms per loop

Für Listen, die nicht sehr groß im Speicher sind, können Sie direkt eine Liste zurückgeben, aber Yield ist besser zur besseren Lesbarkeit (persönliche Präferenz).
Zu den integrierten Generatorfunktionen von Python 2.x gehören die xrange-Funktion, das itertools-Paket usw.

6. Schleifen optimieren

Platzieren Sie Dinge, die außerhalb der Schleife erledigt werden können, beispielsweise nicht doppelt so schnell:

a = range(10000)
size_a = len(a)
%timeit -n 1000 for i in a: k = len(a)
%timeit -n 1000 for i in a: k = size_a
1000 loops, best of 3: 569 µs per loop
1000 loops, best of 3: 256 µs per loop

7. Optimieren Sie die Reihenfolge mehrerer Beurteilungsausdrücke

Für und den, der am wenigsten zufriedenstellend ist Bedingungen sollten zuerst platziert werden, für oder, setzen Sie diejenige, die die meisten Bedingungen erfüllt, zuerst. Beispiel:

a = range(2000) 
%timeit -n 100 [i for i in a if 10 < i < 20 or 1000 < i < 2000]
%timeit -n 100 [i for i in a if 1000 < i < 2000 or 100 < i < 20]   
%timeit -n 100 [i for i in a if i % 2 == 0 and i > 1900]
%timeit -n 100 [i for i in a if i > 1900 and i % 2 == 0]
100 loops, best of 3: 287 µs per loop
100 loops, best of 3: 214 µs per loop
100 loops, best of 3: 128 µs per loop
100 loops, best of 3: 56.1 µs per loop

8. Verwenden Sie Join, um die Zeichenfolgen im Iterator zusammenzuführen

In [1]: %%timeit
  ...: s = &#39;&#39;
  ...: for i in a:
  ...:     s += i
  ...:
10000 loops, best of 3: 59.8 µs per loop

In [2]: %%timeit
s = &#39;&#39;.join(a)
  ...:
100000 loops, best of 3: 11.8 µs per loop

Join hat eine etwa fünffache Verbesserung der Akkumulationsmethode.

9. Wählen Sie die entsprechende Formatierungszeichenmethode

s1, s2 = &#39;ax&#39;, &#39;bx&#39;

%timeit -n 100000 &#39;abc%s%s&#39; % (s1, s2)
%timeit -n 100000 &#39;abc{0}{1}&#39;.format(s1, s2)
%timeit -n 100000 &#39;abc&#39; + s1 + s2
100000 loops, best of 3: 183 ns per loop
100000 loops, best of 3: 169 ns per loop
100000 loops, best of 3: 103 ns per loop

In den drei Fällen ist die %-Methode It ist die langsamste, aber der Unterschied zwischen den dreien ist nicht groß (alle sind sehr schnell). (Ich persönlich denke, % ist am besten lesbar)

10. Tauschen Sie die Werte zweier Variablen aus, ohne Zwischenvariablen zu verwenden

In [3]: %%timeit -n 10000
  a,b=1,2
  ....: c=a;a=b;b=c;
  ....:
10000 loops, best of 3: 172 ns per loop

In [4]: %%timeit -n 10000

a,b=1,2a,b=b,a
  ....:
10000 loops, best of 3: 86 ns per loop

Verwenden Sie a,b=b,a anstelle von c=a;a=b;b=c;, um die Werte von a,b auszutauschen, was mehr als 1-mal schneller sein kann.

11. Die Verwendung von if is

a = range(10000)
%timeit -n 100 [i for i in a if i == True]
%timeit -n 100 [i for i in a if i is True]
100 loops, best of 3: 531 µs per loop
100 loops, best of 3: 362 µs per loop

die Verwendung von if is True ist fast eins schneller als if == Wahre Zeiten.

12. Kaskadenvergleich x < y < ; z ist etwas effizienter und besser lesbar.

13. while 1 ist schneller als while True

x, y, z = 1,2,3

%timeit -n 1000000 if x < y < z:pass
%timeit -n 1000000 if x < y and y < z:pass

1000000 loops, best of 3: 101 ns per loop
1000000 loops, best of 3: 121 ns per loop

while 1 ist viel schneller als while true Der Grund dafür ist, dass True in Python2.x eine globale Variable und kein Schlüsselwort ist.

14. Verwenden Sie ** anstelle von pow

def while_1():
  n = 100000
  while 1:
    n -= 1
    if n <= 0: break

def while_true():
  n = 100000
  while True:
    n -= 1
    if n <= 0: break

m, n = 1000000, 1000000

%timeit -n 100 while_1()
%timeit -n 100 while_true()
100 loops, best of 3: 3.69 ms per loop
100 loops, best of 3: 5.61 ms per loop

**, was mehr als zehnmal schneller ist !

15. Verwenden Sie cProfile, cStringIO und cPickle, um die gleichen Funktionspakete

(entsprechend Profile, StringIO, Pickle)
%timeit -n 10000 c = pow(2,20)
%timeit -n 10000 c = 2**20

10000 loops, best of 3: 284 ns per loop
10000 loops, best of 3: 16.9 ns per loop

Paket in C implementiert, mehr als 10-mal schneller!

16. Verwenden Sie die beste Deserialisierungsmethode

import cPickle
import pickle
a = range(10000)
%timeit -n 100 x = cPickle.dumps(a)
%timeit -n 100 x = pickle.dumps(a)
100 loops, best of 3: 1.58 ms per loop
100 loops, best of 3: 17 ms per loop
Im Folgenden wird die Effizienz der Methoden eval, cPickle und json zum Deserialisieren entsprechender Zeichenfolgen verglichen:

Es ist ersichtlich, dass JSON fast dreimal schneller als cPickle und mehr als 20 Mal schneller als Eval ist.

17. Verwendung von C-Erweiterungen (Extension)

import json
import cPickle
a = range(10000)
s1 = str(a)
s2 = cPickle.dumps(a)
s3 = json.dumps(a)
%timeit -n 100 x = eval(s1)
%timeit -n 100 x = cPickle.loads(s2)
%timeit -n 100 x = json.loads(s3)
100 loops, best of 3: 16.8 ms per loop
100 loops, best of 3: 2.02 ms per loop
100 loops, best of 3: 798 µs per loop
Derzeit gibt es drei Hauptmethoden: CPython (die häufigste Implementierungsmethode von Python), native API, Ctypes, Cython, und cffi , ihre Funktion besteht darin, Python-Programmen den Aufruf dynamischer Linkbibliotheken zu ermöglichen, die aus C kompiliert wurden. Ihre Eigenschaften sind:

CPython native API: Durch die Einführung der Python.h-Header-Datei können Python-Datenstrukturen direkt im entsprechenden C-Programm verwendet werden. Der Implementierungsprozess ist relativ umständlich, hat aber einen relativ großen Anwendungsbereich.
ctypes: wird normalerweise zum Umschließen (Wrapping) von C-Programmen verwendet, sodass reine Python-Programme Funktionen in dynamischen Linkbibliotheken (DLL in Windows oder ähnliche Dateien in Unix) aufrufen können. Wenn Sie eine vorhandene C-Bibliothek in Python verwenden möchten, ist die Verwendung von ctypes eine gute Wahl. Laut einigen Benchmark-Tests ist python2+ctypes die beste Methode.
Cython: Cython ist eine Obermenge von CPython, die den Prozess des Schreibens von C-Erweiterungen vereinfacht. Der Vorteil von Cython besteht darin, dass seine Syntax prägnant ist und es gut mit Bibliotheken wie Numpy kompatibel ist, die eine große Anzahl von C-Erweiterungen enthalten. Die Enabling-Szenarien von Cython zielen im Allgemeinen auf die Optimierung eines bestimmten Algorithmus oder Prozesses im Projekt ab. Bei einigen Tests können die Leistungsverbesserungen um das Hundertfache größer sein.
cffi: cffi ist die Implementierung von ctypes in pypy (Einzelheiten siehe unten) und ist auch mit CPython kompatibel. cffi bietet eine Möglichkeit, C-Klassenbibliotheken in Python zu verwenden. Sie können C-Code direkt in Python-Code schreiben und die Verknüpfung mit vorhandenen C-Klassenbibliotheken unterstützen.
Der Einsatz dieser Optimierungsmethoden zielt im Allgemeinen auf die Optimierung der Leistungsengpassmodule bestehender Projekte ab, wodurch die Betriebseffizienz des gesamten Programms mit wenigen Änderungen am ursprünglichen Projekt erheblich verbessert werden kann.

18. Parallele Programmierung

Aufgrund der Existenz von GIL ist es für Python schwierig, die Vorteile von Multi-Core-CPUs voll auszunutzen. Die folgenden parallelen Modi können jedoch durch das integrierte Modul Multiprocessing erreicht werden:

Mehrere Prozesse: Für CPU-intensive Programme können Sie die Prozesse und des Multiprocessing verwenden Pool Warten Sie, bis die gekapselten Klassen paralleles Rechnen über mehrere Prozesse implementieren. Da jedoch die Kommunikationskosten im Prozess relativ hoch sind, kann die Effizienz von Programmen, die eine große Menge an Dateninteraktionen zwischen Prozessen erfordern, möglicherweise nicht wesentlich verbessert werden.
Multithreading: Für IO-intensive Programme verwendet das Modul multiprocessing.dummy die Multiprocessing-Schnittstelle, um Threading zu kapseln, wodurch die Multithread-Programmierung sehr einfach wird (z. B. Sie kann die Pool-Kartenschnittstelle verwenden, einfach und effizient).
Verteilt: Die Manager-Klasse im Multiprocessing bietet eine Möglichkeit, Daten zwischen verschiedenen Prozessen auszutauschen, und auf dieser Basis können verteilte Programme entwickelt werden.
In verschiedenen Geschäftsszenarien können Sie eines oder eine Kombination aus mehreren auswählen, um die Programmleistung zu optimieren.

19. Der ultimative Killer: PyPy

PyPy ist Python, das mit RPython (einer Teilmenge von CPython) implementiert wurde ist besser als CPythons Implementierung von Python und ist mehr als sechsmal schneller. Der Grund, warum es schnell ist, liegt darin, dass es einen Just-in-Time-Compiler (JIT) verwendet, also einen dynamischen Compiler. Im Gegensatz zu statischen Compilern (wie gcc, javac usw.) verwendet er Daten aus dem laufenden Prozess des Optimierungsprogramms. Aus historischen Gründen wird die GIL immer noch in Pypy beibehalten, aber das laufende STM-Projekt versucht, PyPy ohne die GIL in Python umzuwandeln.

Wenn das Python-Programm C-Erweiterungen (nicht-cffi) enthält, wird der Optimierungseffekt von JIT stark reduziert, sogar langsamer als bei CPython (als bei Numpy). Daher ist es in PyPy besser, reines Python oder die cffi-Erweiterung zu verwenden.

Mit der Verbesserung von STM, Numpy und anderen Projekten glaube ich, dass PyPy CPython ersetzen wird.

20. Verwenden Sie Leistungsanalysetools

Zusätzlich zum oben in ipython verwendeten Timeit-Modul gibt es auch cProfile. Die Verwendung von cProfile ist ebenfalls sehr einfach: python -m cProfile Dateiname.py Dateiname.py ist der Dateiname des auszuführenden Programms. In der Standardausgabe können Sie sehen, wie oft jede Funktion aufgerufen wird So können Performance-Engpässe des Programms gezielt optimiert werden.

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er zum Lernen aller beiträgt. Ich hoffe auch, dass jeder die PHP-Chinesisch-Website unterstützt.

Weitere 20 Tipps, um Ihre Python zum Fliegen zu bringen, und verwandte Artikel finden Sie auf der chinesischen PHP-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