[Python-Leipzig] Pandas unter 7 Minuten!

Mike Müller mmueller at python-academy.de
Fr Aug 14 10:50:54 UTC 2015


Hallo Stefan,

Am 14.08.15 um 08:06 schrieb Stefan Schwarzer:
> Hi,
> 
> On 2015-08-13 23:31, Mike Müller wrote:
>> wir versprochen hier die Fortsetzung vom Dienstag.
> 
> Mike, kannst du vielleicht noch für die Abwesenden ;-) in
> ein paar Sätzen das Problem beschreiben? Es muss ja (vermute
> ich) nicht im Detail sein.

Es geht hauptsächlich um das Umsortieren von Daten.

Eingabe:

* zwei große Textdateien mit stündlichen Werten für ein Jahr in der Form:

       ID1;ID2;ID3;...;ID18000
Datum1;wert1;wert2,;wert3;...;wert18000
Datum2;wert18001;wert18002...;wert36000
Datum3:...
...
Datum8760;wert157662000;...;wert157680000

* eine Datei mit täglich Werten für ein Jahr, also nur 365 Zeilen statt der
365 * 24 = 8760 der anderen beiden. Die Werte sollen durch Auffüllen
auch stündlich werden. Ist dann im Ergebnis also auch so groß wie die beiden
anderen.

Ziel:

HDF5-Datei mit 18000 Gruppen. Jede Gruppe enthält (neben weiteren Daten)
eine Tabelle für eine ID der Form:

Datum1;wert_tab1_1;wert_tab2_1;wert_tab_3_1
Datum2;wert_tab1_2;wert_tab2_2;wert_tab_3_2
Datum3:...
...
Datum8760;wert_tab1_8760;wert_tab2_8760;wert_tab_3_8760

>> Die Pandas-Version ist mit Abstand die schnellste und kann auch helfen
>> Speicher zu sparen.
> 
> Was waren die anderen Ansätze und wie sah es da mit den
> Zeiten und dem Speicher aus?

1. Ansatz "Zeilenweise"

Zeilenweise aus den Textdateien lesen und dann immer eine Zeile in
einer der 18.000 Tabellen schreiben.

Speicherverbrauch: nicht relevant
Laufzeit: 3 - 5 Tage (hochgerechnet)

2. Ansatz "Alles in den Speicher"

Alle Daten in ein Dictionary mit Listen von Listen und dann immer
eine Tabellen vollständig schreiben.

Speicherverbrauch: > 22 GB (hochgerechnet)
Laufzeit: nicht ermittelbar da nur 8 GB Speicher vorhanden

3. "Große Teile nacheinander in den Speicher"

Mehrfaches Lesen des Inputs und immer nur z.B. 2000 IDs nutzen.

Speicherverbrauch: ca. 6 GB
Laufzeit: ca. 2 h 20 min


4. Ansatz "NumPy"

Wie 2 aber mit NumPy-Arrays anstatt Listen.

Speicherverbrauch: ca. 3,5 GB
Laufzeit: < 22 min

Geht auch stückweise wie 3. Ist aber nicht nötig.

5. Ansatz "Pandas"

Quelltext unten.

Speicherverbrauch: ca. 4 GB
Laufzeit: ca. 6,5 min

* Lesen in Pandas-DataFrames
* Umwandeln in NumPy-Record-Arrays
* Schreiben mit einem Python-Funktionsaufruf pro Tabelle

Geht auch stückweise.

>> Ich habe die Input-Dateien immer wieder gelesen, so dass
>> diese wohl vom Betriebssystem im Cache gehalten werden.
> 
> Kommt drauf an, wie viel RAM der Rechner hat. Wenn die
> Datenstrukturen im Python-Prozess groß genug sind,
> "verdrängen" sie den Cache. :-)

Ich habe 8 GB und davon ca. 4 GB für den Prozess benötigt. Die Textdateien
haben zusammen  ca. 1,5 GB. Die Speicherauslastung war bei >80%. Da hat das
Betriebssystem Potential. Wenn ich nach einem Neustart beginne sind es eher
7,5 Minuten statt 6,5. Natürlich für den gleichen Testaufbau. Alles lief mit
einer gewöhnlich Festplatte. Bei einer SSD könnten die Zahlen abweichen.

>> Beim Einlesen aller Gebiete auf einmal braucht das Programm ca. 6,5 Minuten
>> bei etwas über 4 GB Speichernutzung. Bei einer Batch-Größe von 4.000, d.h.
>> bei den etwas über 16.000 Gebieten 5 mal teilweise lesen, steigt die Zeit
>> auf ca. 7,5 Minuten. Das Programm braucht dann aber nur etwas mehr als
>> 1 GB Speicher.
> 
> 6,5 bis 7,5 fürs Einlesen ... Wie war die gesamte Laufzeit?

Das ist die Gesamtlaufzeit. Dabei ist Zeit für das Schreiben länger.
Ich habe noch nicht genau gemessen wie viel länger. Außerdem sind ja noch
Umwandlungsschritte dabei.

>> Hier der relevante Code:
>> [...]
>>
>> @Arnold: Ob eine In-PostgreSQL-Importieren-Variante noch schneller sein
>> könnte. ;)
> 
> Vor allem, wenn man nach dem Einlesen der Daten in die
> Datenbank noch mehr Rechnungen anstellen will, könnte sich
> das durchaus lohnen.

Die Daten sind ja dann in einer HDF5-Datei. Benchmarks für das reine
Lesen zeigen, dass HDF5 teilweise deutlich schneller ist als PostgreSQL:
http://www.pytables.org/usersguide/optimization.html

Da ich die Daten nur einmal importieren muss und dann immer wieder lese,
scheint ein entscheidender Geschwindigkeitsgewinn mit PostgreSQL eher
unwahrscheinlich.

> Was mir noch dazu einfällt (und in Klammern, warum):
> 
> SQLite (sicher langsamer und weniger Features als
> PostgreSQL, aber weniger Aufwand für die Inbetriebnahme ;-) )

Ich denke die Zwischenstufe SQL erzeugen ist sicher nicht zu vernachlässigen.
Pandas ist zum großen Teil in Cython und damit C implementiert. PyTables
ebenso. Damit gehen die Daten aus dem Textformat der Datei direkt, ohne Umwege
über Python, in C-Datenstrukturen und dann von dort, nach Umwandlungsschritten
in diesen Strukturen, wieder auf die Platte in die HDF5-Datei.

> Julia (beliebte Hilfssprache bei SciPy-Nutzern, wie Python
> ziemlich high-level; http://julialang.org/ )

Würde mich interessieren. Kann ich aber überhaupt nicht.

> PyPy (schnelleres Python, aber dafür vermutlich keine
> Nutzung diverser C-Bibliotheken möglich)

Könnte ich mal probieren. Ob das es jedoch Speicher schonend wage ich zu
bezweifeln.

> Fortran 95 oder besser (mutmaßlich sehr gut in Numerik, aber
> soweit ich das Problem verstehe, macht das Einlesen der
> Daten einen großen Anteil der Laufzeit aus)

Ich rechne ja kaum. Deshalb wäre ein solche Lösung wahrscheinlich kaum
schneller als meine mit Pandas und PyTables und dem darunterliegenden C.

> Java (weit verbreitete Universalsprache, soll durch den JIT
> auch sehr schnell sein)

Speicher sparen? ;)

> C (als mutmaßlich "schnelle Sprache", aber C tut sich
> wahrscheinlich niemand an ;-) )

Haben wir schon. Nur halt viel angenehmer verpackt. Da wir ja
64-bit-Zahlen wollen ist auch am Speicherverbrauch nicht so richtig viel
zu drehen. Lässt sich leicht nachrechnen:
>>> 24 * 365 * 18000 * 3 * 8 / (1024**3)
3.5244226455688477

Also ca. 3,5 GB.

> Mit Fortran kenne ich mich besser aus als mit Julia, aber
> die Implementierung in Julia würde mich mehr interessieren.
> 
> Letztlich wäre das aber ein "unfairer" Vergleich, da ich mit
> Python und dessen Optimierungsmöglichkeiten weitaus
> vertrauter bin als mit denen in Julia. In Julia habe ich
> bisher erst ein sehr kleines Programm geschrieben.

Ich kann gern ein Script liefern, das Test-Daten in der richten Größe
erzeugt. ;)

Viele Grüße
Mike

> 
> Viele Grüße
> Stefan
> 
>> Am 12.08.15 um 08:00 schrieb Mike Müller:
>>> Hallo zusammen,
>>>
>>> nach der doch sehr anregenden Diskussion bei unserem Treffen gestern, habe
>>> ich mich an eine Pandas-Version gemacht. Bei etwas über 4 GB Speicherbedarf
>>> war die Sache in weniger als 7 Minuten erledigt. Das ist nochmal dreimal
>>> schneller als meine NumPy-Variante mit den Python-Schleifen. Ich werde auch
>>> noch eine Pandas-Version mit stückweiser Verarbeitung bauen. Ich schicke die
>>> Quelltexte dann hier auf die Liste.
>>>
>>> Viele Grüße
>>> Mike
>>>
>>> _______________________________________________
>>> Python-Leipzig mailing list
>>> Python-Leipzig at python.net
>>> http://starship.python.net/mailman/listinfo/python-leipzig
>>>
>>> Website der Leipzig Python User Group:
>>> http://www.python-academy.de/User-Group/
>>>
>>
>>
>> _______________________________________________
>> Python-Leipzig mailing list
>> Python-Leipzig at python.net
>> http://starship.python.net/mailman/listinfo/python-leipzig
>>
>> Website der Leipzig Python User Group:
>> http://www.python-academy.de/User-Group/
>>
> 
> _______________________________________________
> Python-Leipzig mailing list
> Python-Leipzig at python.net
> http://starship.python.net/mailman/listinfo/python-leipzig
> 
> Website der Leipzig Python User Group:
> http://www.python-academy.de/User-Group/
> 




Mehr Informationen über die Mailingliste Python-Leipzig