Zu den Sprachen "Python", die als dynamisch typisiert klassifiziert sind, gehören "Cython", das Python-Code in C / C ++ konvertiert und kompiliert, und "Numba", das Python mithilfe eines JIT-Compilers beschleunigt. Ich wollte die Effizienz verbessern, indem ich diese in den Python-Code einbaute, der in Forschung und Praktika verwendet wurde, und um mein Verständnis zu vertiefen, versuchte ich, die Verarbeitungsgeschwindigkeit für die tatsächliche einfache Verarbeitung auf jupyter zu vergleichen und zu verifizieren. ..
Dieser Artikel ist kein Einführungsartikel zu Cython. Was ist Cython? Wenn Sie die Grundkenntnisse und die Verwendung kennenlernen möchten, empfehlen wir die folgenden Artikel.
Wenn Sie Meinungen oder Fehler haben, können Sie diese gerne kommentieren. Der experimentelle Code lautet hier.
Ich habe versucht, die Geschwindigkeit zu erhöhen, indem ich die unten gezeigte einfache Funktion "is_prime" (Hauptbeurteilungsfunktion) als Basis verwendet habe. Informationen zum Code Der gleiche Teil wie die Basislinie wird weggelassen. In Bezug auf Cython haben wir den Geschwindigkeitsunterschied je nach Typort überprüft.
baseline
def find_factor(n):
answer = n
for i in range (2,n):
if n % i == 0:
answer = i
break
return answer
def is_prime(n):
if find_factor (n) != n:
return False
else:
return True
from numba import jit
@jit
def find_factor0 (n):
#Abkürzung
def is_prime0 (n):
#Abkürzung
Kompilieren Sie einfach mit Cython, ohne den Code zu ändern
% load_ext Cython
%% cython
def find_factor1(n):
#Abkürzung
def is_prime1(n):
#Abkürzung
Geben Sie alle Variablen ein
% load_ext Cython
%% cython
def find_factor2(int n):
cdef int answer = n
cdef int i
#Abkürzung
def is_prime2(n):
#Abkürzung
Typdefinition nur i in for-Anweisung
% load_ext Cython
%% cython
def find_factor2_1(n):
answer = n
cdef int i
#Abkürzung
def is_prime2_1(n):
#Abkürzung
Geben Sie nur die Antwort ein
% load_ext Cython
%% cython
def find_factor2_2(n):
cdef int answer = n
#Abkürzung
def is_prime2_2(n):
#Abkürzung
Typdefinition nur Argument n
% load_ext Cython
%% cython
def find_factor2_3(int n):
#Abkürzung
def is_prime2_3(int n):
#Abkürzung
Typdefinition außer Argument n
% load_ext Cython
%% cython
def find_factor2_4(n):
cdef int answer = n
cdef int i
#Abkürzung
def is_prime2_4(n):
#Abkürzung
Definieren Sie die Findfactor-Funktion mit cdef
% load_ext Cython
%% cython
cdef int find_factor3(int n):
cdef int answer = n
cdef int i
#Abkürzung
def is_prime3(n):
#Abkürzung
In allen Experimenten wurde die zur Bestimmung der Primzahl 131071 erforderliche Zeit mit "% timeit" gemessen. Die durchschnittliche Zeit und Standardabweichung jedes 100-mal ausgeführten Prozesses sind wie folgt.
Baseline | Numba | Cython1 Keine Codeänderung |
Cython2 Die Funktion selbst |
Cython3 Alle Variablen in der Funktion |
|
---|---|---|---|---|---|
Zeit(ms) | 8.69±0.2 | 0.49±0.0 | 5.34±0.1 | 0.43±0.0 | 0.43±0.0 |
Cython2-1 nur für Aussage i |
Cython2-2 antworte nur |
Cython2-3 Nur Argument n |
Cython2-4 Anders als Argument n |
|
---|---|---|---|---|
Zeit(ms) | 4.81±0.1 | 5.27±0.4 | 6.62±0.1 | 4.85±0.1 |
Nach dem Ergebnis von Cython2 ist es für Cython am schnellsten, wenn alle Typinformationen definiert sind. Betrachten wir nun die Änderung der Geschwindigkeit aufgrund des Unterschieds in der Typdefinition. Wie Sie aus dem Unterschied zwischen Cython2-1 und Cython2-2 ersehen können, ist ersichtlich, dass Ersteres (typdefinierend die Iteratorvariable i
) wesentlich zum Effekt beiträgt, wenn die Variable typdefiniert ist. Dies liegt daran, dass die in der for-Schleife durchgeführte Inkrementberechnung für jede Operation eine Typprüfung enthält (entspricht dem Vorgang, bei dem jedes Mal "int .__ add__" aufgerufen wird). Wenn sie also häufig ausgeführt wird, ist der Overhead hoch, aber die Typdefinition Es wird angenommen, dass dieser Overhead durch die Berechnung von "i + 1" direkt in der C-Sprache beseitigt wird.
Unter Berücksichtigung von Cython2-3 ist die Typdefinition mit nur Argumenten langsamer als Cython1. Ich dachte, es sollte schneller sein, indem die dynamische Typprüfung in den Argumenten beim Aufrufen der Funktion find factor
entfernt wird, aber es war tatsächlich langsamer. In Anbetracht der Tatsache, dass aufgrund der Definition des Argumenttyps und nicht aufgrund dieser Effekte ein Kompilierungsaufwand anfällt, haben wir die folgenden Funktionen "hoge1, hoge2" definiert, die sich nur durch das Vorhandensein oder Fehlen der Definition des Argumenttyps unterscheiden, und die Geschwindigkeiten verglichen. Es gab jedoch einen kleinen Unterschied.
%% cython
def hoge1 (n,a,b,c):
return
def hoge2 (int n, int a, int b, int c):
return
Selbst mit der obigen Funktion betrug der Unterschied ungefähr 10 ns (10 Millionen Schleifen werden gedreht und der Unterschied sollte kein Fehler vom Standardabweichungswert sein), und außerdem gab es fast keinen Unterschied, wenn es ein Argument gab, also diesen Overhead Es wird nicht davon ausgegangen, dass der Unterschied auf zurückzuführen ist. Sie können auch aus Cython2-4 ersehen, dass es schneller ist, die Argumente einzugeben, wenn alle Variablen im Suchfaktor eingegeben werden. Aus den obigen Gründen ist die Ursache für die Verzögerung in Cython2-3 die Dynamik, wenn nur eine der Operationen "n% i == 0" eingegeben wird, als wenn weder "n" noch "i" eingegeben werden. Ich denke, die Schlussfolgerung ist, dass der Overhead des Schecks größer ist, aber ich habe den Grund nicht verstanden.
Das Ergebnis der Eingabe des Suchfaktors selbst aus Cython3 unterschied sich nicht wesentlich von Cython2. Die arithmetische Verarbeitung selbst in "Find Factor" ist dieselbe wie in Cython2, daher bin ich überzeugt. In Cython3 sollte jedoch der Overhead beim Aufrufen von "Find Factor" entfernt werden, sodass das Aufrufen von "Find Factor" einmal fast keinen Unterschied macht, aber ich denke, dass Cython3 beim Aufrufen von viel schneller sein wird. Ich habe die folgende Funktion erstellt und überprüft.
%% cython
cdef int find_factor3_1(int n):
#Abkürzung
def find_factor3_2(int n):
#Abkürzung
def is_prime_inf1(n,m):
for i in range (m):
find_factor3_1 (n)
def is_prime_inf2(n,m):
for i in range (m):
find_factor3_2 (n)
find factor3-1(cdef) | find factor3-2(def) | |
---|---|---|
Zeit | 158 μs | 3.1s |
Ein signifikanter Unterschied trat auf, wenn erwartungsgemäß eine große Anzahl von "Suchfaktoren" aufgerufen wurde. Daher wurde festgestellt, dass der Aufwand für das Aufrufen durch Typdefinition des Suchfaktors selbst beseitigt wird.
Ich habe versucht, die Geschwindigkeit mit dem Matrixprodukt als Basis zu erhöhen.
def dot(a, b):
n = len(a)
l = len(a[0])
m = len(b[0])
c = np.zeros((n, m))
for i in range(n):
for j in range(m):
for k in range(l):
c[i][j] += a[i][k] * b[k][j]
return c
from numba import jit
@jit
def nm_dot(a, b):
#Abkürzung
Derzeit wird die Typdefinition basierend auf den bisherigen Kenntnissen durchgeführt und mit Cython implementiert. Wechseln Sie zu [i] [j]
→ [i, j]
, um zu beschleunigen, Typdefinitionen zusammen zu deklarieren, zu range
→ xrange
zu wechseln, die Prüffunktion auszuschalten usw. Wir entwickeln.
%%cython
import numpy as np
cimport numpy as np
cython: boundscheck=False
cython: wraparound=False
cpdef np.ndarray c_dot(np.ndarray a, np.ndarray b):
cdef np.ndarray c
cdef int n,l,m,i,j,k
n = len(a)
l = len(a[0])
m = len(b[0])
c = np.zeros((n, m))
for i in xrange(n):
for j in xrange(m):
for k in xrange(l):
c[i,j] += a[i,k] * b[k,j]
return c
Die Typdefinition für das Numpy-Array wurde unter Bezugnahme auf die offizielle Dokumentation strenger gestaltet.
cpdef np.ndarray[dtype=float,ndim = 2] c_dot2(np.ndarray[dtype=float,ndim = 2] a, np.ndarray[dtype=float,ndim = 2] b):
cdef np.ndarray[dtype=double,ndim = 2] c
#Abkürzung
In diesem Experiment wurde eine 100 * 100 zufällige Numpy-Matrix erzeugt und mit "% timeit" wie in Experiment 1 gemessen. Die durchschnittlichen und Standardabweichungen für 10 Prozesse bei der zeitaufwändigen Implementierung von Python- und Cython 1- und 100-Prozessen in anderen Fällen sind wie folgt.
Python | Python(Numpy) | Numba | Cython1 | Cython2 | |
---|---|---|---|---|---|
Zeit(ms) | 1220±54 | 0.033 | 1.1 | 729±16 | 2.5 |
In Cython1 ist es etwas schneller als in der naiven Python-Implementierung, in Cython2 jedoch viel schneller. Wenn in der Implementierung in Cython1 der Typ jedes Numpy-Arrays als np.ndarray definiert ist, werden der tatsächliche Typ und die tatsächliche Dimension noch nicht bestimmt und es wird schließlich eine dynamische Überprüfung durchgeführt, was der Verarbeitung entspricht, wenn Cython nicht verwendet wird. Es wird angenommen, dass dies daran liegt, dass es gespeichert ist. Wie Sie den Ergebnissen entnehmen können, war Numpy bei der Berechnung des Matrixprodukts überwältigend schnell. Erstens habe ich Numpy oft benutzt, aber ich wusste nicht, warum es so schnell ist, also habe ich ein wenig recherchiert (ich hätte es zuerst tun sollen). Es wurde gesagt, dass es intern in C-Sprache implementiert wurde. Auch hier kam das Tippen heraus und ich erkannte, wie wichtig das Tippen ist. Es ist besser, numpy zu verwenden, da es zumindest für Berechnungen gilt, die als Modul in numpy wie Matrixprodukt implementiert sind, und Cython, wenn es nicht als numpy-Modul implementiert ist und nicht realisiert werden kann, ohne tatsächlich viele Male auf das numpy-Array zuzugreifen. Es scheint gut zu benutzen.
Durch dieses Experiment tippt die praktische Einführung von Cython nicht die dunklen Wolken ein, sondern verbessert effizient die Leistung, während die Vorteile von Python wie Codeflexibilität und Lesbarkeit sowie die Verwendung hervorragender Bibliotheken wie Numpy berücksichtigt werden. Ich fand es gut, die Orte zu identifizieren, an denen dies möglich sein würde.
Recommended Posts