Beliebte Suchanfragen
//

DuckDB vs. DataFrame Bibliotheken

1.12.2025 | 10 Minuten Lesezeit

Hinweis: Der folgende Artikel wurde auf Englisch erstellt und nachträglich maschinell auf Deutsch übersetzt.

Update 10.12.25 – Nach hilfreichen Hinweisen von Polars-Entwickler Thijs Nieuwdorp nach der ersten Veröffentlichung dieses Artikels konnten wir unsere Nutzung der veralteten .count()-Funktion in Polars überarbeiten und sie durch die korrekte .len()-Funktion ersetzen.

Die effiziente Verarbeitung großer, strukturierter Datensätze steht im Zentrum moderner Datenanalyse. Pandas ist seit Langem die Standard-DataFrame-Bibliothek für Python und wird für ihre Flexibilität, das umfangreiche Ökosystem und die intuitive API geschätzt. Mit wachsenden Datensätzen, die den Arbeitsspeicher übersteigen, und steigenden Performance-Anforderungen gewinnen neuere Tools wie Polars und DuckDB an Bedeutung. Während Polars und Pandas DataFrame-Bibliotheken sind und DuckDB eine eingebettete SQL-Analyse-Engine ist, verfolgen alle drei das Ziel, großskalige Datenverarbeitung schneller und einfacher zu machen – durch parallele Ausführung, Lazy-Computing und Out-of-Core-Verarbeitung.

Dieser Artikel vergleicht Pandas, Polars und DuckDB hinsichtlich Performance, Speicherverbrauch, Skalierbarkeit, Ergonomie und Interoperabilität. Wir zeigen auf, wann ein DataFrame-zentrierter Workflow glänzt, wann SQL-basierte Tools besser sind und wie sich diese Werkzeuge in realen Pipelines ergänzen.

Hintergrund

Pandas

Pandas ist nach wie vor die am weitesten verbreitete DataFrame-Bibliothek und bietet eine ausgereifte API sowie nahtlose Integration in den wissenschaftlichen Python-Stack. Sie eignet sich besonders für Prototyping, Datenbereinigung und explorative Analysen. Standardmäßig führt pandas Operationen eager und meist single-threaded aus; typische Workflows setzen voraus, dass die Daten in den Arbeitsspeicher passen – sehr große Dateien (z.B. mehrere Dutzend GB) können daher zu Speicherproblemen oder Abstürzen führen, sofern man nicht chunked oder downsampled. Neuere Versionen (pandas 2.x) bieten Copy-on-Write und optionale Arrow-basierte Datentypen, die die Speichereffizienz und Interoperabilität verbessern, aber pandas ist keine Out-of-Core- oder parallele Analyse-Engine.

Polars

Polars ist eine moderne, spaltenorientierte DataFrame-Bibliothek, geschrieben in Rust mit Python-Bindings, und nutzt Apache Arrow für eine effiziente Speicherrepräsentation. Sie unterstützt standardmäßig Multithreading und bietet sowohl eager als auch lazy APIs. Die Lazy-Engine ermöglicht Query-Optimierung (Projection und Predicate Pushdown) und kann in Kombination mit Streaming-Ausführung Datensätze verarbeiten, die größer als der Arbeitsspeicher sind. Nicht alle Operationen sind streambar, aber für mittelgroße bis große Datensätze führt die Kombination aus Parallelisierung, Lazy-Optimierung und Streaming oft zu erheblichen Geschwindigkeits- und Speichervorteilen. Das Ökosystem wächst noch und einige pandas-spezifische Features fehlen, aber die rasante Entwicklung und optionale GPU-Beschleunigung machen Polars zu einer attraktiven Wahl für performante Datenverarbeitung.

DuckDB

DuckDB ist eine moderne, eingebettete OLAP-SQL-Engine, geschrieben in C++ und ausgelegt für hochperformante analytische Abfragen. Sie nutzt eine vektorisierte, pipeline-basierte Ausführung mit kostenbasierter Optimierung, unterstützt Multithreading und kann Datensätze verarbeiten, die größer als der Arbeitsspeicher sind, indem sie Streaming-Scans und automatisches Auslagern auf die Festplatte für Operationen wie Sortierungen und Aggregationen verwendet. DuckDB kann SQL direkt über CSV-Dateien sowie über In-Memory-DataFrames (pandas, Polars) und Arrow-Tabellen ausführen, was einen nahtlosen SQL-zentrierten Workflow ermöglicht. Es glänzt bei komplexen Joins, Aggregationen und Group-Bys. Die Performance, Skalierbarkeit und Interoperabilität machen DuckDB zu einem leistungsstarken Baustein für Analyse-Pipelines.

Methodik

Benchmarking-Prinzipien

Benchmarking bedeutet mehr als nur dieselbe Operation auf verschiedenen Systemen auszuführen. Um faire und reproduzierbare Ergebnisse zu gewährleisten, folgen wir Best Practices für Benchmarks, machen alle Skripte und Umgebungen einsehbar und halten uns an Richtlinien von Experten auf diesem Gebiet, etwa DuckDBs eigenen Hannes Mühleisen.

Wir benchmarken jedes Tool mit seinem idiomatischen, eingebauten Workflow für große, strukturierte Daten – ohne spezielle Tuning-Maßnahmen oder externe Erweiterungen. DuckDB wird als SQL-zentrierte Engine genutzt, die Dateien direkt abfragt. Polars wird über die Lazy-Scan-API mit aktiviertem Streaming verwendet, was der dokumentierte Ansatz für effiziente Verarbeitung großer Dateien ist. Pandas wird mit dem Standard-DataFrame-Aufbau (read_csv) genutzt, da es keine integrierte Out-of-Core- oder Lazy-Engine besitzt. Dieser Ansatz spiegelt wider, wie Praktiker die Aufgabe in jedem Tool typischerweise lösen, ohne an Konfigurationsschrauben (Thread-Anzahl, PRAGMAs, alternative Parser, GPU-Backends) zu drehen.

Testaufbau

Wir benchmarkten zentrale OLAP-Operationen – Filtern und Zählen – anhand eines realen E-Commerce-Datensatzes (CSV, 9 GB, 67 Millionen Zeilen, 9 Spalten). Alle Tests wurden auf einem MacBook Pro 2021 (M1 Max, 32 GB RAM) unter Python durchgeführt. Unser Benchmarking-Tool sorgt für konsistente und reproduzierbare Kommandozeilenausführung, wobei Nutzer Tool, Operation, Benchmark-Modus (cold oder hot) und Anzahl der Durchläufe angeben können. Die Ergebnisse werden mit matplotlib visualisiert, und wir berichten über zentrale statistische Kennzahlen: Mittelwert, Standardabweichung und Variationskoeffizient.

Der Speicherverbrauch wird gemessen, indem der belegte Speicher unmittelbar vor und nach jedem Funktionsaufruf aufgezeichnet wird; die Differenz entspricht dem von der Funktion genutzten Speicher. Hot Runs nutzen den OS-Page-Cache und Bibliothekspuffer. Cold Runs werden in isolierten Prozessen mit zufälligem Dateizugriff ausgeführt; der macOS-Page-Cache wird nicht explizit geleert, sodass die Ergebnisse „kälter“, aber nicht vollständig ungecached sind. Für beide Modi wird jede Operation 10-mal wiederholt.

Wir veröffentlichen die exakten Skripte und Umgebungsdetails, sodass Leser die idiomatischen Pfade wie beschrieben in unserem Repository nachstellen können. Es sind keine besonderen Flags oder Erweiterungen nötig, außer der Installation der Standardpakete; die Ergebnisse sollten auf ähnlicher Hardware und OS-Konfiguration stabil sein.

Ergebnisse

Cold Runs

Die Ergebnisse der Cold-Benchmarks unterstreichen die Speichereffizienz von DuckDB. Polars kommt der Ausführungszeit und dem Speicherverbrauch von DuckDB fast gleich, während Pandas in beiden Metriken deutlich hinterherhinkt.

Pandas zeigt einen hohen Overhead durch das Parsen der CSV und das Materialisieren eines DataFrames. In Cold Runs werden dabei etwa 10 GB (entsprechend der ~10 GB großen CSV) verbraucht, und das Filtern nach Kaufereignissen erhöht den Verbrauch um etwa 1 GB.

1Line #    Mem usage    Increment  Occurrences   Line Contents
2=============================================================
3     7    142.2 MiB    142.2 MiB           1   @profile
4     8                                         def filtering_counting():
5     9  13112.7 MiB  12970.5 MiB           1       df = pd.read_csv(dataset_path)
6    10  14491.0 MiB   1378.3 MiB           1       purchases = df[df["event_type"] == "purchase"]
7    11  14491.1 MiB      0.1 MiB           1       print("Count:", len(purchases))

Im Eager-Modus materialisiert Polars typischerweise einen DataFrame aus dem gesamten Datensatz, was zu erheblichem Speicherverbrauch führen kann. Im Gegensatz dazu lädt Polars bei Verwendung eines Lazy-CSV-Scans nicht den gesamten Datensatz in den Speicher, sondern verarbeitet nur die für die jeweilige Operation benötigten Zeilen. Dieser Ansatz ermöglicht effiziente Batch-Verarbeitung statt vollständigem Laden, was zu klaren Speicherersparnissen führt. Zusätzlich unterstützt die Lazy-Engine von Polars Streaming-Ausführung, wodurch der Speicherverbrauch weiter reduziert wird, indem die Daten in kleineren, handhabbaren Blöcken verarbeitet werden.

1Line #    Mem usage    Increment  Occurrences   Line Contents
2=============================================================
3     7    142.8 MiB    142.8 MiB           1   @profile
4     8                                         def filtering_counting():
5     9    143.2 MiB      0.4 MiB           1       lf = pl.scan_csv(dataset_path)
6    10   1681.7 MiB   1538.5 MiB           1       result = lf.filter(pl.col("event_type") == "purchase").count().collect(streaming=True)
7    11   1681.9 MiB      0.2 MiB           1       print(result)

Anfangs dachten wir, dies sei das Maximum für Polars. Doch nachdem wir diesen Artikel auf LinkedIn veröffentlicht hatten, meldete sich Thijs Nieuwdorp, Developer Relations Engineer bei Polars, und wies auf einen Fehler hin:

Ich habe mir das etwas genauer angesehen und festgestellt, dass das DuckDB COUNT(*) nicht gut auf unser .count() übertragbar ist. Letzteres zählt die Anzahl der Nicht-Null-Elemente in jeder Spalte, wodurch wir die gesamte Datei scannen müssen. Stattdessen könntest du .count() durch .select(pl.len()) ersetzen und erhältst dasselbe Ergebnis wie bei DuckDB – eine einzelne Spalte mit einem Wert, der Länge des DataFrames.

Wir haben diesen Vorschlag gerne umgesetzt, was den Speicherverbrauch von Polars – wie von Thijs korrekt beobachtet – um etwa 1 GB reduzierte:

1Line #    Mem usage    Increment  Occurrences   Line Contents
2=============================================================
3     7    142.1 MiB    142.1 MiB           1   @profile
4     8                                         def filtering_counting():
5     9    142.5 MiB      0.4 MiB           1       lf = pl.scan_csv(dataset_path)
6    10    592.7 MiB    450.3 MiB           1       result = lf.filter(pl.col("event_type") == "purchase").select(pl.len()).collect(streaming=True)
7    11    593.0 MiB      0.2 MiB           1       print(result)

DuckDB fragt die CSV direkt ab, benötigt für den gesamten Prozess nur etwa 300 MB und liefert die Ergebnisse schneller, da es Late Materialization und vektorbasierte Verarbeitung mit Predicate Pushdown und vektorisierten Pipelines nutzt.

1Line #    Mem usage    Increment  Occurrences   Line Contents
2=============================================================
3     7    142.2 MiB    142.2 MiB           1   @profile
4     8                                         def filtering_counting():
5     9    428.3 MiB    286.1 MiB           1       duckdb.sql(f"SELECT COUNT(*) AS purchase_count FROM read_csv_auto('{dataset_path}') WHERE event_type = 'purchase'").show()

Hot Runs

In Hot-Benchmarks schrumpft DuckDBs Vorsprung. Pandas reduziert nach den ersten Durchläufen den Speicherverbrauch und wechselt häufig zwischen Freigabe und Verbrauch von Speicher – bleibt aber dennoch deutlich über den anderen Tools. Auch bei Polars schwankt der Speicherverbrauch, profitiert aber von einem niedrigeren Grundniveau und häufigen Speicherfreigaben.

Polars kann so den Abstand zu DuckDB schließen.

Vergleicht man Hot- und Cold-Runs, bleibt der Speicherverbrauch von DuckDB konstant niedrig, was auf starke Optimierung hindeutet. Keines der Tools erzielt in Hot-Runs nennenswerte Zeitersparnisse. Über 10 Hot-Runs hinweg spart Pandas beeindruckende 10 GB gegenüber den Cold-Runs – verbraucht aber immer noch deutlich mehr Speicher als Polars und DuckDB in deren Cold-Runs. Während der Hot-Runs arbeitet Polars so effizient wie DuckDB.

Diskussion

Diese Benchmarks verdeutlichen, wie Architektur und Ausführungsmodelle das Verhalten von Pandas, Polars und DuckDB bei großen Datensätzen bestimmen – die Ergebnisse hängen vom Datenformat, Schema (String vs. numerisch), Filterselektivität und den Speichereigenschaften ab; unsere Erkenntnisse beziehen sich auf eine ~10 GB große CSV auf einer lokalen SSD.

Pandas ist durchweg langsamer und weniger vorhersehbar im Speicherverbrauch, da es aus der CSV stets einen vollständigen DataFrame eager materialisiert und überwiegend single-threaded arbeitet. Viele Operationen sind zwar auf Array-Ebene via NumPy vektorisiert, aber pandas fehlt ein Query-Optimizer und eine datenbankähnliche, vektorisierte/pipeline-basierte Engine, ebenso wie integriertes Lazy- oder Out-of-Core-Processing. Für große Dateien bedeutet das: höherer Spitzenverbrauch, mehr Python-Objekt-Overhead (besonders bei Strings) und begrenzte Parallelisierung. Fortgeschrittene Muster (z.B. read_csv mit chunksize/Iteratoren) können den Speicherverbrauch senken, ändern aber das Programmiermodell und sind außerhalb unseres idiomatischen Vergleichs.

Polars, in Rust auf Arrow gebaut, nutzt Multithreading und eine spaltenorientierte Expressions-Engine, um pandas in Sachen Geschwindigkeit zu übertreffen. Die Lazy-API mit Streaming-Modus kann für viele Abfragen die vollständige Materialisierung vermeiden, indem Filter/Projektionen in den Scan geschoben werden. Streaming ist nicht universell: Operationen, die Kontext über Batches hinweg benötigen oder Zwischenstände materialisieren, können dennoch mehrere GB RAM verbrauchen. In unserem Filter-und-Zähl-Workload und unserer Umgebung lag der Spitzenverbrauch mit Lazy+Streaming bei etwa ~0,5 GB; tatsächliche Werte variieren je nach Schema und Selektivität. Insgesamt verbessert Polars die Speicher- und Laufzeiteffizienz gegenüber pandas deutlich und erreicht bei großen, On-Disk-Analysen in Hot-Runs sogar DuckDB-Niveau.

DuckDB liefert durchgehend niedrigen Speicherverbrauch und starke Performance, indem es einen kostenbasierten Optimierer mit vektorisierter, pipeline-basierter Ausführung und Late Materialization kombiniert. Es schiebt Prädikate und Projektionen in den Dateiscan, streamt Daten ohne vollständige Materialisierung von Tabellen und hält den Spitzen-RSS klein und stabil. Der In-Process-Betrieb mit C++-Datenstrukturen vermeidet Python-Objekt-Overhead, und die Nutzung mehrerer Kerne erfolgt automatisch – ideal für schnelle Ad-hoc-Analysen großer CSV-Dateien.

Warum ist Pandas so langsam?

DuckDB und Polars verarbeiten nur die benötigten Spalten, arbeiten in cache-freundlichen Vektoren und nutzen parallele Pipelines; pandas baut stets vollständige DataFrames eager auf und hat weder Query-Optimizer noch eine datenbankähnliche Ausführungsengine. Trotz Array-Vektorisierung via NumPy verursacht die typische Nutzung von pandas mehr Rechenaufwand, mehr Speichertraffic und begrenzte Parallelisierung im großen Maßstab. Ohne integriertes Lazy-/Out-of-Core-Processing bleibt der idiomatische Pfad von pandas speichergebunden. Chunked Reading kann helfen, ändert aber den Workflow und ist nicht direkt mit SQL-/Lazy-Pipelines vergleichbar.

Fazit

Pandas, Polars und DuckDB haben jeweils ihre Stärken in unterschiedlichen Bereichen moderner Analytik. Für kleine bis mittlere Datensätze und umfangreiche Bibliotheksintegration bleibt pandas eine produktive Standardlösung. Für größere oder performancekritische Workloads bieten Polars und DuckDB durch parallele Ausführung und Out-of-Core-/Lazy-Pipelines erhebliche Vorteile. In unseren CSV-basierten Benchmarks lieferte DuckDB die konstanteste Performance und den niedrigsten Spitzenverbrauch, da es Dateien direkt abfragt, ohne Tabellen vollständig zu materialisieren. Dank der Hinweise des Polars-Teams konnte Polars seinen Speicherverbrauch um etwa 1 GB senken und positioniert sich nun mit nur rund 100 MB Abstand direkt hinter DuckDB. Das zeigt, wie kleine Implementierungsdetails einen großen Einfluss auf reale Performance und Speichereffizienz haben können.

Dieser Wissensaustausch zwischen Tool-Entwicklern und Anwendern ist entscheidend für den Fortschritt im Daten-Ökosystem. Offener Dialog – wie das Feedback von Thijs Nieuwdorp bei Polars – hilft nicht nur, Missverständnisse zu korrigieren und die Benchmark-Genauigkeit zu verbessern, sondern beschleunigt auch die Verbreitung von Best Practices in der Community. Durch das Teilen von Erkenntnissen und offene Zusammenarbeit entwickeln sich Tools und Nutzer gemeinsam weiter, was zu robusteren, effizienteren und benutzerfreundlicheren Lösungen führt. Solche Interaktionen unterstreichen die Bedeutung von Transparenz, Bescheidenheit und kontinuierlichem Lernen im sich schnell wandelnden Feld der Datenanalyse.

DuckDB ist am stärksten für SQL-zentrierte, On-Disk-Analysen; Polars glänzt bei schnellen DataFrame-Transformationen (insbesondere mit Lazy + Streaming); pandas bleibt ideal für interaktives Data Munging bei moderaten, im Speicher gehaltenen Daten. Die effektivste Strategie ist eine Kombination: Jedes Tool dort einsetzen, wo seine Architektur zum Problem passt. Die Ergebnisse können je nach Datenformat (CSV vs. Parquet), Schema (String vs. numerisch), Filterselektivität und Hardware variieren.

Wenn Sie daran interessiert sind, die Leistungsfähigkeit von DuckDB in der Cloud zu nutzen, melden Sie sich zu unserem On-Demand-Hands-on Workshop: Einführung in MotherDuck für eine vollständige praktische Einführung an!

Beitrag teilen

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//
Jetzt für unseren Newsletter anmelden

Alles Wissenswerte auf einen Klick:
Unser Newsletter bietet dir die Möglichkeit, dich ohne großen Aufwand über die aktuellen Themen bei codecentric zu informieren.