6 Stimmen

Was ist die plattform- und versionsunabhängigste Möglichkeit, eine schnelle Schleife für die Verwendung in Python zu erstellen?

Ich schreibe eine wissenschaftliche Anwendung in Python mit einer sehr prozessorintensiven Schleife als Kernstück. Ich möchte diese Anwendung so weit wie möglich optimieren und dabei die Unannehmlichkeiten für die Endbenutzer so gering wie möglich halten. Diese werden sie wahrscheinlich als unkompilierte Sammlung von Python-Skripten verwenden und Windows, Mac und (hauptsächlich Ubuntu) Linux einsetzen.

Es ist derzeit in Python mit einem Hauch von NumPy geschrieben, und ich habe den Code unten eingefügt.

  1. Gibt es eine Lösung, die einigermaßen schnell ist und keine Kompilierung erfordert? Dies scheint der einfachste Weg zu sein, um die Plattformunabhängigkeit zu erhalten.
  2. Wenn Sie etwas wie Pyrex verwenden, die Kompilierung erfordert, gibt es eine einfache Möglichkeit, viele Module zu bündeln und haben Python wählen zwischen ihnen je nach erkannten OS und Python-Version? Gibt es eine einfache Möglichkeit, eine Sammlung von Modulen zu erstellen, ohne dass man Zugang zu jedem System mit jeder Python-Version benötigt?
  3. Eignet sich eine Methode besonders gut für die Optimierung mit mehreren Prozessoren?

(Falls es Sie interessiert: Die Schleife besteht darin, das Magnetfeld an einem bestimmten Punkt in einem Kristall zu berechnen, indem man die Beiträge einer großen Anzahl von nahegelegenen magnetischen Ionen addiert, die als winzige Stabmagnete behandelt werden. Im Grunde genommen wird eine riesige Summe von diese .)

# calculate_dipole
# -------------------------
# calculate_dipole works out the dipole field at a given point within the crystal unit cell
# ---
# INPUT
# mu = position at which to calculate the dipole field
# r_i = array of atomic positions
# mom_i = corresponding array of magnetic moments
# ---
# OUTPUT
# B = the B-field at this point

def calculate_dipole(mu, r_i, mom_i):
    relative = mu - r_i
    r_unit = unit_vectors(relative)
    #4pi / mu0 (at the front of the dipole eqn)
    A = 1e-7
    #initalise dipole field
    B = zeros(3,float)

    for i in range(len(relative)):
        #work out the dipole field and add it to the estimate so far
        B += A*(3*dot(mom_i[i],r_unit[i])*r_unit[i] - mom_i[i]) / sqrt(dot(relative[i],relative[i]))**3
    return B

10voto

Ray Punkte 4371

Sie können dies viel, viel schneller ausführen, wenn Sie die Schleife eliminieren und die vektorisierten Operationen von Numpy verwenden. Legen Sie Ihre Daten in Numpy-Arrays der Form (3,N) und versuchen Sie Folgendes:

import numpy as np

N = 20000
mu = np.random.random((3,1))
r_i = np.random.random((3,N))
mom_i = np.random.random((3,N))

def unit_vectors(r):
     return r / np.sqrt((r*r).sum(0))

def calculate_dipole(mu, r_i, mom_i):
    relative = mu - r_i
    r_unit = unit_vectors(relative)
    A = 1e-7

    num = A*(3*np.sum(mom_i*r_unit, 0)*r_unit - mom_i)
    den = np.sqrt(np.sum(relative*relative, 0))**3
    B = np.sum(num/den, 1)
    return B

Dies läuft bei mir etwa 50 Mal schneller als eine for-Schleife.

4voto

whatnick Punkte 5286

Numpy verwendet einige native Optimierungen für die Array-Verarbeitung. Sie können Numpy-Arrays mit Cython um einige Geschwindigkeitssteigerungen zu erzielen.

3voto

Dave Kirby Punkte 24272

Ihr Python-Code ließe sich wahrscheinlich etwas beschleunigen, wenn Sie Ihre Schleife durch einen Generatorausdruck ersetzen und alle Nachschlagevorgänge von mom_i[i], relative[i] und r_unit[i] entfernen, indem Sie mit itertools.izip alle drei Sequenzen parallel durchlaufen.

d.h. ersetzen

B = zeros(3,float)

for i in range(len(relative)):
    #work out the dipole field and add it to the estimate so far
    B += A*(3*dot(mom_i[i],r_unit[i])*r_unit[i] - mom_i[i]) / sqrt(dot(relative[i],relative[i]))**3
return B

mit:

from itertools import izip
...
return sum((A*(3*dot(mom,ru)*ru - mom) / sqrt(dot(rel,rel))**3 
            for mom, ru, rel in izip(mom_i, r_unit, relative)),
           zeros(3,float)) 

Dies ist IMHO auch lesbarer, da die Kerngleichung nicht überall mit [i] überladen ist

Ich vermute jedoch, dass dies nur marginale Vorteile im Vergleich zu tun, die ganze Funktion in einer kompilierten Sprache wie Cython erhalten.

2voto

Justin Peel Punkte 46114

Eine einfache, aber signifikante Beschleunigung besteht darin, die Multiplikation mit A außerhalb der Summe durchzuführen. Sie können dann einfach das B mit der Summe multiplizieren, wenn Sie es zurückgeben:

for i in range(len(relative)):
    #work out the dipole field and add it to the estimate so far
    B += (3*dot(mom_i[i],r_unit[i])*r_unit[i] - mom_i[i]) / sqrt(dot(relative[i],relative[i]))**3

return A*B

Bei 20.000 zufälligen Dipolen ergab dies eine Beschleunigung von etwa 8 %.

Abgesehen von dieser einfachen Beschleunigung würde ich die Verwendung von Cython (das im Allgemeinen der Verwendung von Pyrex vorgezogen wird) oder Weave von Scipy empfehlen. Werfen Sie einen Blick auf die Leistung Python für einige Beispiele und Vergleiche von verschiedenen Möglichkeiten, Numpy/Scipy zu beschleunigen.

Wenn Sie versuchen wollen, dies parallel zu machen, würde ich empfehlen, sich Scipys Parallele Programmierung um loszulegen.

Es ist gut, einen anderen Physiker bei SO zu sehen. Es gibt nicht sehr viele hier.

Bearbeiten:

Ich beschloss, dies als Herausforderung anzunehmen, um einige Cython-Fähigkeiten zu entwickeln, und erreichte eine etwa 10-fache Zeitverbesserung gegenüber einer für Psyco optimierten Version. Lassen Sie mich wissen, wenn Sie meinen Code sehen möchten.

Bearbeiten2:

Okay, ich bin zurückgegangen und habe herausgefunden, was die Dinge in meiner Cython-Version verlangsamt hat. Jetzt ist der Geschwindigkeitszuwachs weit über 100x. Wenn Sie einen weiteren Faktor von 2x oder so über Rays beschleunigte Numpy-Version wollen oder brauchen, lassen Sie es mich wissen und ich werde meinen Code posten.

Cython-Quellcode:

Hier ist der Cython-Code, den ich zusammengebastelt habe:

import numpy as np
cimport numpy as np
cimport cython
cdef extern from "math.h":
    double sqrt(double theta)
ctypedef np.float64_t dtype_t

@cython.boundscheck(False)
@cython.wraparound(False)
def calculate_dipole_cython(np.ndarray[dtype_t,ndim=2,mode="c"] mu, 
                            np.ndarray[dtype_t,ndim=2,mode="c"] r_i, 
                            np.ndarray[dtype_t,ndim=2,mode="c"] mom_i):
    cdef Py_ssize_t i
    cdef np.ndarray[dtype_t,ndim=1,mode="c"] tmp = np.empty(3,np.float64)
    cdef np.ndarray[dtype_t,ndim=1,mode="c"] relative = np.empty(3,np.float64)
    cdef double A = 1e-7
    cdef double C, D, F
    cdef np.ndarray[dtype_t,ndim=1,mode="c"] B = np.zeros(3,np.float64)
    for i in xrange(r_i.shape[0]):
        relative[0] = mu[0,0] - r_i[i,0]
        relative[1] = mu[0,1] - r_i[i,1]
        relative[2] = mu[0,2] - r_i[i,2]
        C = relative[0]*relative[0] + relative[1]*relative[1] + relative[2]*relative[2]
        C = 1.0/sqrt(C)
        D = C**3
        tmp[0] = relative[0]*C
        F = mom_i[i,0]*tmp[0]
        tmp[1] = relative[1]*C
        F += mom_i[i,1]*tmp[1]
        tmp[2] = relative[2]*C
        F += mom_i[i,2]*tmp[2]
        F *= 3
        B[0] += (F*tmp[0] - mom_i[i,0])*D
        B[1] += (F*tmp[1] - mom_i[i,1])*D
        B[2] += (F*tmp[2] - mom_i[i,2])*D
    return A*B

Ich glaube, ich habe es schon ziemlich optimiert, aber vielleicht kann man noch ein bisschen mehr herausholen. Man kann vielleicht noch die np.zeros und np.empty mit direkten Aufrufen der Numpy C API ersetzen, aber das sollte keinen großen Unterschied ausmachen. So wie es aussieht, bringt dieser Code eine 2-3-fache Verbesserung gegenüber dem für Numpy optimierten Code, den Sie haben. Allerdings müssen Sie die Zahlen korrekt übergeben. Die Arrays müssen im C-Format sein (das ist der Standard für Numpy-Arrays, aber in Numpy ist die Transposition eines C-formatierten Arrays ein Fortran-formatiertes Array).

Zum Beispiel, um den Code von Ihre andere Frage müssen Sie die np.random.random((3,N)) s mit np.random.random((N,3)) . Außerdem, `

r_test_fast = reshape_vector(r_test) 

muss geändert werden in

r_test_fast = np.array(np.matrix(r_test))

Diese letzte Zeile kann einfacher/schneller gemacht werden, aber das wäre meiner Meinung nach eine verfrühte Optimierung.

Wenn Sie Cython noch nicht benutzt haben und nicht wissen, wie man das kompiliert, dann lassen Sie es mich wissen und ich helfe Ihnen gerne.

Abschließend empfehle ich, sich folgende Informationen anzusehen dieses Papier . Ich habe sie als Leitfaden für meine Optimierungen verwendet. Der nächste Schritt wäre die Verwendung von BLAS-Funktionen, die den SSE2-Befehlssatz nutzen, die Verwendung der SSE-API oder die Verwendung der Numpy-C-API, die eine Schnittstelle zu den SSE2-Funktionen hat. Sie können auch eine Parallelisierung in Betracht ziehen.

1voto

Ira Baxter Punkte 91118

Python ist nicht für Hochleistungsberechnungen gedacht. Schreiben Sie die Kernschleife in C und rufen Sie sie von Python aus auf.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X