2010-11-11 5 views
6

Der Kontext: meine Python-Code übergeben Arrays von 2D-Scheitelpunkte zu OpenGL.Was ist der schnellste Weg in Python ein c-Array aus einer Liste von Tupel von Floats zu erstellen?

Ich testete zwei Ansätze, einen mit Ctypes, den anderen mit struct, wobei letzterer mehr als zweimal schneller ist.

from random import random 
points = [(random(), random()) for _ in xrange(1000)] 

from ctypes import c_float 
def array_ctypes(points): 
    n = len(points) 
    return n, (c_float*(2*n))(*[u for point in points for u in point]) 

from struct import pack 
def array_struct(points): 
    n = len(points) 
    return n, pack("f"*2*n, *[u for point in points for u in point]) 

Jede andere Alternative? Irgendein Hinweis, wie man solchen Code beschleunigt (und ja, das ist ein Flaschenhals meines Codes)?

+0

Ich übersetzte diese Frage auch an die Newsgroup gmane.comp.python.opengl.user, die ähnliche Antworten wie unten lieferte. –

Antwort

2

Sie könnten versuchen Cython. Für mich ergibt dies:

function  usec per loop: 
       Python Cython 
array_ctypes 1370 1220 
array_struct 384  249 
array_numpy  336  339 

So Numpy gibt nur 15% Vorteil auf meiner Hardware (alter Laptop mit Windows XP), während Cython etwa 35% ergibt (ohne zusätzliche Abhängigkeit in Ihrem verteilten Code).

Wenn Sie Ihre Anforderung lösen, dass jeder Punkt ein Tupel von Schwimmern ist, und einfach ‚Punkte‘ machen eine abgeflachte Float-Liste:

def array_struct_flat(points): 
    n = len(points) 
    return pack(
     "f"*n, 
     *[ 
      coord 
      for coord in points 
     ] 
    ) 

points = [random() for _ in xrange(1000 * 2)] 

dann die resultierende Ausgabe ist die gleiche, aber das Timing geht weiter nach unten:

function   usec per loop: 
        Python Cython 
array_struct_flat   157 

Cython könnte auch, wenn jemand klüger als ich als dies wesentlich besser geeignet sein wollte Erklärungen statischen Typ, um den Code hinzuzufügen. (Das Ausführen von 'cython -a test.pyx' ist dafür von unschätzbarem Wert, es erzeugt eine HTML-Datei, die Ihnen anzeigt, wo das langsamste (gelbe) einfache Python in Ihrem Code ist, im Gegensatz zu Python, das in reines C (weiß) konvertiert wurde ich breitete den Code oben aus auf so viele Zeilen, weil die Färbung pro-line durchgeführt wird, so hilft es, sie um sich auszubreiten ähnlich)

Voll Cython Anweisungen sind hier:. http://docs.cython.org/src/quickstart/build.html

Cython könnte produzieren ähnliche Leistungsvorteile in Ihrer gesamten Codebasis, und unter idealen Bedingungen, bei richtiger statischer Typisierung, kann die Geschwindigkeit um Faktoren von zehn oder hundert verbessert werden.

0

Sie können array (man beachte auch den Generator Ausdruck anstelle der Liste Verständnis) verwenden:

array("f", (u for point in points for u in point)).tostring() 

Eine weitere Optimierung wäre es, die Punkte von Anfang an abgeflacht zu halten.

+0

Ich habe in meinen ersten Versuchen versucht, Generatoren und es stellt sich heraus, dass es die Funktionen verlangsamt. – rndblnch

+0

(und es verlangsamt auch diese Array-Version). Übrigens ist die array-basierte Lösung selbst mit Listenverständnis immer noch 20% langsamer als die Struct-Version ... – rndblnch

3

Sie können numpy Arrays an PyOpenGL übergeben, ohne dass ein Overhead entsteht. (Das data Attribut des numpy Array ist ein Puffer, der auf die zugrunde liegenden C-Datenstruktur zeigt, die die gleichen Informationen wie das Array enthält Sie bauen)

import numpy as np 
def array_numpy(points): 
    n = len(points) 
    return n, np.array(points, dtype=np.float32) 

Auf meinem Computer ist dies etwa 40% schneller als der struct -basierte Ansatz.

+0

Beeindruckend! Ich wollte meinem Code keine numpige Abhängigkeit hinzufügen, aber es sieht so aus, als wäre es das wert. (Randbemerkung: keine Angabe des Parameters dtype tötet die Perf um einen Faktor 10) – rndblnch

+0

Kann diese Technik weiter verbessert werden, indem das numpy-Array im Voraus erstellt wird und dann die Elemente wie erforderlich aktualisiert werden. Ich stelle mir Situationen vor, in denen Vertices meist statisch sind, aber irgendwann müsste ein Teil davon für Animationen aktualisiert werden. –

+0

Sie können auch zusätzliche Vorteile aus der Verwendung von numpy erhalten, um die Arrays zu manipulieren, sobald sie existieren. z.B. Sie könnten einem Array von Positionen ein Array von Geschwindigkeiten hinzufügen. Dies kann besonders gut für Partikelsysteme sein, bei denen Ihr Python-Code keinen häufigen Zugriff auf den Wert der resultierenden Positionen benötigt. –

1

Es gibt eine andere Idee, über die ich gestolpert bin. Ich habe keine Zeit, es jetzt zu profilieren, aber falls es jemand anderes tut:

# untested, but I'm fairly confident it runs 
# using 'flattened points' list, i.e. a list of n*2 floats 
points = [random() for _ in xrange(1000 * 2)] 
c_array = c_float * len(points * 2) 
c_array[:] = points 

Das heißt, zuerst schaffen wir die ctypes Array aber bevölkern es nicht. Dann füllen wir es mit der Slice-Notation. Leute, die schlauer sind als ich, sagen mir, dass das Zuweisen zu einer Scheibe wie diese der Leistung helfen kann.Es erlaubt uns, eine Liste oder iterable direkt auf dem RHS der Zuweisung zu übergeben, ohne die * iterable Syntax zu verwenden, die einige Zwischengerangel des iterable durchführen würde. Ich vermute, dass dies in den Tiefen von pyglets Batches passiert.

Vermutlich könnten Sie einfach einmal c_array erstellen und dann jedes Mal, wenn sich die Punkte-Liste ändert, neu zuordnen (die letzte Zeile im obigen Code).

Es gibt wahrscheinlich eine alternative Formulierung, die die ursprüngliche Definition von Punkten (eine Liste von (x, y) Tupel.) Akzeptiert Etwas wie folgt aus:

# very untested, likely contains errors 
# using a list of n tuples of two floats 
points = [(random(), random()) for _ in xrange(1000)] 
c_array = c_float * len(points * 2) 
c_array[:] = chain(p for p in points) 
+0

Danke für das Feedback @DanielLemire. Haben Sie aus Interesse beide Ansätze aus dieser Antwort versucht? –

+0

Siehe meine aktualisierte Antwort auf diese Frage. –

1

Wenn die Leistung ein Problem, Sie nicht wollen, ctypes-Arrays mit der Sternoperation zu verwenden (z. B. (ctypes.c_float * size)(*t)).

In meinem Test pack ist am schnellsten gefolgt von der Verwendung des Moduls array mit einer Besetzung der Adresse (oder mit der Funktion from_buffer).

import timeit 
repeat = 100 
setup="from struct import pack; from random import random; import numpy; from array import array; import ctypes; t = [random() for _ in range(2* 1000)];" 
print(timeit.timeit(stmt="v = array('f',t); addr, count = v.buffer_info();x = ctypes.cast(addr,ctypes.POINTER(ctypes.c_float))",setup=setup,number=repeat)) 
print(timeit.timeit(stmt="v = array('f',t);a = (ctypes.c_float * len(v)).from_buffer(v)",setup=setup,number=repeat)) 
print(timeit.timeit(stmt='x = (ctypes.c_float * len(t))(*t)',setup=setup,number=repeat)) 
print(timeit.timeit(stmt="x = pack('f'*len(t), *t);",setup=setup,number=repeat)) 
print(timeit.timeit(stmt='x = (ctypes.c_float * len(t))(); x[:] = t',setup=setup,number=repeat)) 
print(timeit.timeit(stmt='x = numpy.array(t,numpy.float32).data',setup=setup,number=repeat)) 

Der array.array Ansatz ist etwas schneller als Jonathan Hartley Ansatz in meinem Test, während der numpy Ansatz Hälfte hat etwa die Geschwindigkeit:

python3 convert.py 
0.004665990360081196 
0.004661010578274727 
0.026358536444604397 
0.0028003649786114693 
0.005843495950102806 
0.009067213162779808 

Der Netto-Gewinner ist Packung.

+0

Fabelhaft. Reproduzierbare Vergleichsmessungen - beste Antwort auf der Seite. –

+0

Mit Timeit (Nummer = 10), sehe ich eine große Menge an Variation in der Reihenfolge der Timings. Ich musste es auf 1000 erhöhen, bevor sie sich in einer ziemlich konsistenten Reihenfolge niederließen. –

+0

Mit Daniels Skript habe ich auch das Paket gemessen, das zu den schnellsten zählt. Allerdings bin ich neugierig, weil die '* t'-Syntax im Aufruf von 'pack' beispielsweise bedeutet, dass die Liste 't' in ein Tupel für die Argumente von 'pack' entpackt wird. Das hört sich so an, als gäbe es hier noch eine gewisse Inneffizienz und könnte möglicherweise verbessert werden. –

Verwandte Themen