Ich habe versucht, die Beschleunigung von Python durch Cython zu verifizieren und zu analysieren

Einführung

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.

Experiment 1 Vergleich mit der Funktion is_prime

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
Beschleunigen Sie mit Numba
from numba import jit
@jit
def find_factor0 (n):
    #Abkürzung
def is_prime0 (n):
    #Abkürzung
Beschleunigung mit Cython 1 (Cython1)

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
Beschleunigung mit Cython 2 (Cython2)

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
Beschleunigen mit Cython 2-1 (Cython2-1)

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
Beschleunigung mit Cython 2-2 (Cython2-2)

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
Beschleunigen mit Cython 2-3 (Cython2-3)

Typdefinition nur Argument n

% load_ext Cython
%% cython
def find_factor2_3(int n):
    #Abkürzung
def is_prime2_3(int n):
    #Abkürzung
Beschleunigung mit Cython 2-4 (Cython2-4)

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
Beschleunigung mit Cython 3 (Cython3)

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.

Vergleich der Geschwindigkeit der Funktion is_prime
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
Vergleich der Geschwindigkeit nach Typdefinition in Cython
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)
Vergleich, wenn der Suchfaktor 10.000 Mal aufgerufen wird
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.

Experiment 2 Vergleich nach Matrixprodukt

Ich habe versucht, die Geschwindigkeit mit dem Matrixprodukt als Basis zu erhöhen.

Implementiert von Python
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
Implementierung durch Numba
from numba import jit
@jit
def nm_dot(a, b):
    #Abkürzung
Implementierung durch Cython 1 (Cython1)

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
Implementierung durch Cython 2 (Cython2)

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.

Vergleich der Matrixproduktgeschwindigkeiten
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.

abschließend

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.

Referenzmaterial

Recommended Posts

Ich habe versucht, die Beschleunigung von Python durch Cython zu verifizieren und zu analysieren
Ich habe versucht, die Yin- und Yang-Klassifikation hololiver Mitglieder durch maschinelles Lernen zu überprüfen
Ich habe versucht, das Ergebnis des A / B-Tests mit dem Chi-Quadrat-Test zu überprüfen
Ich habe versucht, die Neujahrskarte selbst mit Python zu analysieren
Ich habe versucht, die Sprecheridentifikation mithilfe der Sprechererkennungs-API von Azure Cognitive Services mit Python zu überprüfen. # 1
Ich habe versucht, die Sprecheridentifikation mithilfe der Sprechererkennungs-API von Azure Cognitive Services in Python zu überprüfen. # 2
Ich habe versucht, die String-Operationen von Python zusammenzufassen
Ich habe versucht, die statistischen Daten der neuen Corona mit Python abzurufen und zu analysieren: Daten der Johns Hopkins University
Ich habe versucht, die Entropie des Bildes mit Python zu finden
[Python] Ich habe versucht, die folgende Beziehung von Twitter zu visualisieren
Ich möchte die Natur von Python und Pip kennenlernen
Ich habe versucht, die Unterschiede zwischen Java und Python aufzuzählen
Ich habe versucht, das Artikel-Update des Livedoor-Blogs mit Python und Selen zu automatisieren.
Ich habe versucht, die Verarbeitungsgeschwindigkeit mit dplyr von R und pandas von Python zu vergleichen
[Einführung in Python] Ich habe die Namenskonventionen von C # und Python verglichen.
Ich habe versucht, die Effizienz der täglichen Arbeit mit Python zu verbessern
Ich habe Web Scraping versucht, um die Texte zu analysieren.
Ich habe versucht, das Bild durch Klicken mit der rechten und linken Maustaste in den angegebenen Ordner zu verschieben
[Python] Ich habe versucht, den Pitcher zu analysieren, der keinen Treffer und keinen Lauf erzielt hat
Ich habe versucht, die Trapezform des Bildes zu korrigieren
Ich habe versucht, das Datetime-Modul von Python zu verwenden
Ich habe versucht, die optimale Route des Traumlandes durch (Quanten-) Tempern zu finden
Ich habe versucht, die Phase der Geschichte mit COTOHA zu extrahieren und zu veranschaulichen
Ich habe versucht, den Inhalt jedes von Python pip gespeicherten Pakets in einer Zeile zusammenzufassen
Ich möchte die Gefühle von Menschen analysieren, die sich treffen und zittern wollen
Ich habe versucht, die Negativität von Nono Morikubo zu analysieren. [Vergleiche mit Posipa]
Qiita Job Ich habe versucht, den Job zu analysieren
Ich habe versucht, die Standardrolle neuer Mitarbeiter mit Python zu optimieren
[Linux] Ich habe versucht, die sichere Bestätigungsmethode von FQDN (CentOS7) zu überprüfen.
Ich habe versucht, die Filminformationen der TMDb-API mit Python abzurufen
Ich habe versucht, die Texte von Hinatazaka 46 zu vektorisieren!
Django super Einführung von Python-Anfängern! Teil 2 Ich habe versucht, die praktischen Funktionen der Vorlage zu nutzen
Ich habe versucht, E-Mails von Node.js und Python mithilfe des E-Mail-Zustelldienstes (SendGrid) von IBM Cloud zuzustellen!
Ich habe versucht, das Update von "Hameln" mit "Beautiful Soup" und "IFTTT" zu benachrichtigen.
[Python] Ich habe versucht, das Mitgliederbild der Idolgruppe mithilfe von Keras zu beurteilen
Ich habe versucht, die Tweets von JAWS DAYS 2017 mit Python + ELK einfach zu visualisieren
Ich habe versucht, die 100-Yen-Lagerstätte von Rakuten-Pferderennen (Python / Selen) zu automatisieren.
Ich habe versucht, das Vorhandensein oder Nichtvorhandensein von Schnee durch maschinelles Lernen vorherzusagen.
Ich habe versucht, die Daten des Laptops durch Booten unter Ubuntu zu retten
Ich habe versucht, den Code des Python-Anfängers (Schüler der Mittelstufe) zu überarbeiten.
Ich habe versucht, den G-Test und die E-Qualifikation durch Training ab 50 zu bestehen
Ich habe versucht, die Literatur des neuen Corona-Virus mit Python automatisch an LINE zu senden
Ich habe versucht, die in Python installierten Pakete grafisch darzustellen
Ich habe versucht zusammenzufassen, wie man Matplotlib von Python verwendet
Ich habe versucht, die Grundform von GPLVM zusammenzufassen
Ich habe versucht, eine CSV-Datei mit Python zu berühren
Ich habe versucht, Soma Cube mit Python zu lösen
Ich habe mir die Versionen von Blender und Python angesehen
[Python] Ich habe versucht, die Top 10 der Lidschatten grafisch darzustellen
Ich habe versucht, den negativen Teil von Meros zu löschen
Ich habe versucht, das Problem mit Python Vol.1 zu lösen
[Python] Ich habe versucht, Json von Tintenfischring 2 zu bekommen
Ich habe versucht, die Stimmen der Sprecher zu klassifizieren
[Python & SQLite] Ich habe den erwarteten Wert eines Rennens mit Pferden im 1x-Gewinnbereich ① analysiert