PolarsとPandas:パフォーマンスとメモリのベンチマーク

Table of Contents
長年、PandasはPythonのデータ処理において誰もが認める王者でした。私たちは皆、Pandasが提供するDataFrame APIを愛用し、10年以上にわたってデータサイエンスのワークフローを支えてきました。しかし、正直に言いましょう。データセットがメガバイトからギガバイト(そしてテラバイト)へと増大するにつれて、Pandasは時代遅れになり始めました。そのシングルスレッドの性質と莫大なメモリオーバーヘッドは、大きな頭痛の種となりました。Pandasで簡単な操作を行うだけでも、データセットのサイズの3倍から5倍のRAMを簡単に消費してしまいます。これは主に、即時評価(eager evaluation)と際限のない中間コピーが原因です。そこにPolarsが登場しました。Rustで書かれたPolarsは、最新のマルチスレッドでメモリ効率の高い処理のためにゼロから構築されたDataFrameエンジンです。PandasではなくPolarsを選択することは、もはや速度だけの問題ではありません。それはAWSの請求額に直接影響します。ETLコンテナでOOM(Out Of Memory)クラッシュに悩まされた経験があるなら、私が何を言っているのかよくわかるでしょう。
Polarsはメモリ集約型操作でなぜPandasを凌駕するのか?
Polarsがメモリ集約型操作でPandasを凌駕するのは、Apache Arrowのカラムナーメモリ表現と並列Rust実行カーネルを利用して、中間データコピーを排除しているためです。

Pandasは伝統的にブロックベースのNumPyメモリマネージャーに依存しており、文字列カラム、オブジェクト型、欠損値には重いPythonオブジェクトラッパーが必要です。Pandasでフィルタリングや集計操作を実行すると、エンジンは各中間変換ステップで基になる配列のコピーを作成します。Polarsは、このレガシーなメモリレイアウトをApache Arrowメモリフォーマット仕様に置き換えます。Apache Arrowは、連続したカラムナーメモリブロックにデータを格納し、標準化された欠損値の有効性ビットマップを備えているため、キャッシュに優しいCPU SIMDベクトル計算が可能になります。さらに、Polarsは可能な限り変換中のメモリコピーを回避し、ゼロコピーのスライスビューとArrowメモリポインタを使用します。大量の分析エンジンを構築するデータアーキテクトは、低いRAMフットプリントを維持するためにArrowメモリ表現に依存しています。Arrowカラムナーメモリレイアウトに切り替えると、文字列処理のメモリオーバーヘッドが大幅に削減されることがわかるでしょう。
以下のコード例は、PandasとPolarsの間でデータ変換のメモリフットプリントがどのように異なるかを示しています。
# Pandas Eager Transformation Pattern (High Memory Overhead)
import pandas as pd
import numpy as np
def process_pandas_dataframe(filepath: str) -> pd.DataFrame:
# Eager load: Reads entire CSV into memory using Python object types
df = pd.read_csv(filepath)
# Intermediate copy 1: Filtering creates an entirely new DataFrame allocation
filtered = df[df["transaction_amount"] > 100.0]
# Intermediate copy 2: Column assignment allocates additional memory block
filtered["tax_amount"] = filtered["transaction_amount"] * 0.08
# Intermediate copy 3: GroupBy aggregation allocates separate result table
result = filtered.groupby("category_id")[["transaction_amount", "tax_amount"]].sum().reset_index()
return result
対照的に、Polarsはメモリアクセスをベクトル化し、利用可能なCPUコア間で計算を並列化するRustエンジンを使用します。
# Polars High-Performance Processing Pattern (Zero-Copy Architecture)
import polars as pl
def process_polars_dataframe(filepath: str) -> pl.DataFrame:
# Polars uses Apache Arrow memory layout and parallel multi-threaded scanning
df = pl.read_csv(filepath)
# Expressions are evaluated concurrently across CPU threads without intermediate copies
result = (
df.filter(pl.col("transaction_amount") > 100.0)
.with_columns((pl.col("transaction_amount") * 0.08).alias("tax_amount"))
.group_by("category_id")
.agg([
pl.col("transaction_amount").sum(),
pl.col("tax_amount").sum()
])
)
return result
Apache ArrowのメモリレイアウトとRustのメモリ安全性保証を活用することで、Polarsは複雑なDataFrame変換を最小限のメモリ消費で処理します。Arrowカラムナー構造を採用しない場合、大規模な文字列データセットの処理は過剰なRAM割り当てを消費します。主要なクラウドETLパイプラインがコンピューティングノードのコスト削減のためにPolarsに移行しているのを目にしています。
さらに、Polarsは文字列キャッシュテーブルを使用してカテゴリデータの処理を最適化します。Pandasでは、カテゴリカラムは手動での辞書エンコーディングステップを必要とすることがよくあります。Polarsはグローバル文字列キャッシュを自動的に管理し、オブジェクトポインタのオーバーヘッドなしで文字列カラムに対する高速な結合(join)およびグループ化(group-by)計算を可能にします。
さらに、Polarsの式はCPUキャッシュにアラインされています。Rustの実行エンジンは、CPUのL1/L2キャッシュに直接収まるメモリブロックでデータベクトルを処理するため、集計ループ中のメモリバス待機状態を最小限に抑えます。
加えて、PolarsはArrowベクトルの構築中にネイティブなメモリアラインメント検証をサポートしており、数値配列がハードウェアアクセラレーション処理のためにSIMDレジスタ境界にアラインされていることを保証します。
さらに、Polarsは計算ステップ中の中間Python GIL(Global Interpreter Lock)取得を回避します。基になる式はコンパイルされたネイティブRustで実行されるため、数値変換はPythonインタープリタのロックを待つことなくCPUスレッド間で処理されます。
最後に、Polarsは並列CSVおよびParquetファイル解析をすぐにサポートしており、利用可能なすべてのCPUコアで入力ファイルを同時に読み込むことで、データロードのレイテンシを最小限に抑えます。
遅延評価はPolarsのクエリ実行グラフをどのように最適化するのか?
遅延評価(lazy evaluation)は、完全なクエリパイプラインを分析し、フィルタ操作を並べ替え、メモリ割り当ての前に未使用のカラムをプルーニングすることで、Polarsのクエリ実行グラフを最適化します。

Pandasは排他的に即時モード(eager mode)で動作し、ステートメントが実行されるとコードを1行ずつ評価します。即時評価は、エンジンが複数の操作にわたる実行ステップを最適化することを妨げます。例えば、開発者が50カラムのCSVファイルをPandasにロードし、10行後にフィルタリングする場合、Pandasは不要な行を破棄する前に50カラムすべてをメモリにロードします。Polarsはlazy()を介して呼び出される遅延実行APIを導入しています。LazyFrameを使用すると、Polarsは操作をすぐに実行するのではなく、抽象的な論理クエリプランを構築します。.collect()が呼び出されると、Polarsクエリオプティマイザは実行グラフを書き換え、述語プッシュダウン(predicate pushdown)と射影プッシュダウン(projection pushdown)の最適化を適用します。分析ワークロードを分析するデータベースエンジニアは、ディスクI/Oスループットを削減するためのクエリグラフ最適化を重視します。大規模なデータパイプラインで遅延評価を有効にしていない場合、読み込まれないカラムにCPUメモリ帯域幅を無駄にしています。
Polarsの遅延クエリパイプラインで述語プッシュダウンとカラム射影最適化がどのように機能するかを考えてみましょう。
import polars as pl
def execute_optimized_lazy_query(parquet_path: str) -> pl.DataFrame:
# Construct logical query plan without reading parquet file contents into memory
lazy_plan = (
pl.scan_parquet(parquet_path)
.filter(pl.col("region") == "US-EAST")
.filter(pl.col("is_active") == True)
.select(["user_id", "region", "revenue"])
.group_by("region")
.agg(pl.col("revenue").sum().alias("total_revenue"))
)
# Collect triggers query optimization:
# 1. Projection Pushdown: Only loads user_id, region, revenue columns from disk
# 2. Predicate Pushdown: Pushes region/is_active filters down into Parquet scanner
optimized_df = lazy_plan.collect()
return optimized_df
以下の表は、Polarsの遅延エンジンによって自動的に実行される主要なクエリ最適化パスの概要を示しています。
| 最適化戦略 | 運用メカニズム | パフォーマンス上の利点 |
|---|---|---|
| 述語プッシュダウン | フィルタ操作をファイルスキャン層に移動 | 一致しないデータブロックのディスクからの読み込みをスキップ |
| 射影プッシュダウン | 要求されたクエリカラムのみを識別して読み込み | ディスクI/OとRAMメモリ使用量を最大90%削減 |
| 型強制 | メモリ割り当て前に数値データ型を最適化 | 不要なfloat64変換を防止 |
| 共通部分式除去 | クエリグラフで計算された式の結果を再利用 | 冗長なCPU算術演算を排除 |
| 結合順序変更 | カーディナリティ統計に基づいてテーブル結合を並べ替え | 中間ハッシュテーブルのメモリサイズを削減 |
遅延クエリ最適化により、Polarsは即時Pandas処理を桁違いに上回る実行速度を達成できます。大規模なParquetファイルに遅延スキャンを使用しない場合、アプリケーションは不要なカラムを不必要にRAMにロードします。だからこそ、遅延評価グラフはPythonデータエンジニアリングにとって大きな進歩を意味します。
さらに、Polarsは最適化された物理実行計画を出力する.explain()メソッドを提供します。開発者は、長いバッチジョブを実行する前に、フィルタ述語がストレージ層に適切にプッシュダウンされていることを確認するためにクエリ計画を検査できます。
さらに、遅延評価により、ディレクトリツリー全体での複数ファイルデータセットスキャンがシームレスに可能になります。Polarsは数百のパーティション化されたParquetファイルを同時にスキャンし、ファイル境界を越えてフィルタ述語を自動的に適用します。
加えて、Polarsの遅延実行エンジンは、連続するフィルタ条件を自動的にマージし、単一のCPU命令パスで実行される簡素化された比較式を生成します。
さらに、遅延クエリグラフにより、Polarsはスキーマ統計に基づいて結合操作を並べ替えることができ、結合実行中のメモリ消費を最小限に抑えるために、より小さなテーブルをハッシュ結合の右側に配置します。
最後に、遅延クエリ実行により、複数の分析変換をデータに対する単一の最適化されたパスに結合することができ、中間ディスクスピル書き込みを回避します。
PolarsのストリーミングAPIはシステムRAMより大きなデータセットをどのように処理するのか?
PolarsのストリーミングAPIは、システムRAMより大きなデータセットを、メモリマップされたチャンクデータバッチ上でクエリプランを実行し、完全なテーブルをメモリにロードすることなく直接出力ファイルに書き込むことで処理します。

利用可能な物理RAMを超えるデータセットを扱う場合、従来のPandasコードはOOM(Out-Of-Memory)例外で失敗します。Pandasでメモリ枯渇を解決するには、chunksizeイテレータを使用して手動でチャンク処理ループを記述する必要があり、複雑なボイラープレートコードが発生します。Polarsは、そのストリーミング実行エンジンを通じて、アウトオブコアデータ処理をネイティブに解決します。.collect()にstreaming=Trueを渡すと、Polarsはデータをチャンクされたストリーミングバッチで処理するように指示されます。エンジンは、ディスクからクエリオペレータを通じてデータバッチをストリームし、完全なテーブルをRAMにロードすることなく、結果をシンク(出力先)に直接書き込みます。クラウドデータパイプラインを管理するインフラエンジニアは、軽量なコンテナインスタンスで大規模なデータセットを処理するためにストリーミング実行を利用します。パイプラインがストリーミングバッチ実行をサポートしていない場合、大規模なデータセットは予期しないコンテナの再起動を引き起こします。
PolarsのストリーミングシンクAPIを使用して、50ギガバイトのデータセットをラップトップで処理する方法を以下に示します。
import polars as pl
def process_large_dataset_out_of_core(input_parquet: str, output_parquet: str) -> None:
# Configure lazy scanning pipeline over multi-gigabyte dataset
lazy_query = (
pl.scan_parquet(input_parquet)
.filter(pl.col("transaction_status") == "COMPLETED")
.with_columns(
(pl.col("amount") * pl.col("exchange_rate")).alias("converted_amount")
)
.group_by(["account_id", "currency"])
.agg([
pl.col("converted_amount").sum().alias("total_account_spend"),
pl.col("transaction_id").count().alias("transaction_count")
])
)
# Execute query in streaming mode, writing output directly to Parquet file
lazy_query.sink_parquet(
output_parquet,
compression="snappy",
maintain_order=False
)
print(f"Streaming execution completed successfully. Saved to {output_parquet}")
Polarsのストリーミングパイプラインを使用することで、データエンジニアは、メモリ不足エラーに遭遇することなく、控えめなクラウドインフラストラクチャで大規模なデータセットを処理できます。大規模なデータセットをストリーミングしない場合、メモリ消費はファイルサイズに比例して増加し、プロセスのメモリ制限を超過するまで続きます。ストリーミング実行により、アウトオブコアデータ変換が高速かつ簡単に保守できることがわかるでしょう。
さらに、Polarsのストリーミングエンジンはメモリマップされたバッファを自動的に管理します。チャンクされたデータバッチを処理する際、チャンクの処理が完了すると、メモリマップされたページは動的にOSカーネルに解放されます。
さらに、Polarsは大規模なデータセットにわたるストリーミング結合と集計をサポートし、ストリーミングパス中に内部ハッシュテーブルを効率的に維持します。
加えて、Polarsのストリーミングシンクは、圧縮されたParquet出力を増分的に書き込むことを可能にし、高スループットのETLデータパイプライン実行中のローカルストレージ消費を削減します。
さらに、ストリーミングデータパイプラインは、パーティション化されたHiveディレクトリを並列で処理し、中間バッチ状態をメモリに蓄積することなく、処理されたチャンク出力をターゲットのストレージ場所に書き込むことができます。
最後に、ストリーミング実行は、Amazon S3やGoogle Cloud Storageのようなクラウドオブジェクトストレージソースと直接統合されており、完全なファイルを最初にローカルディスクにダウンロードすることなく、リモートのParquetファイルに対するストリーミング変換を可能にします。
実際の集計と結合の速度を示すベンチマークとは?
ベンチマークは、Polarsがピークメモリのごく一部を使用しながら、グループ化クエリをPandasよりも最大10倍高速に実行する、実際の集計と結合の速度を示しています。

PolarsとPandasのパフォーマンスの違いを同じワークロードで評価するために、2000万行の合成データセット(メモリ内で約1.6 GB)でベンチマークを実行しました。テストは、Python 3.13、Pandas 2.2(PyArrowバックエンド有効)、Polars 1.0を実行する8コアワークステーションで実施されました。ベンチマークでは、フィルタリング、複数カラムのグループ化集計、2テーブルの内部結合という3つの一般的な分析ワークフローにおける実行時間とピークRAM割り当てを測定しました。ベンチマークメトリクスを分析するパフォーマンステストチームは、マルチスレッドCPU使用率パターンに細心の注意を払います。最新のArrowエンジンに対して分析クエリのベンチマークを行っていない場合、大規模な実行速度向上を見逃しています。
ベンチマークデータは、2つのライブラリ間の顕著なパフォーマンスの違いを浮き彫りにしています。
| 分析ワークロード | Pandas 2.2 実行時間 | Polars 1.0 実行時間 | ピークメモリ (Pandas) | ピークメモリ (Polars) |
|---|---|---|---|---|
| フィルタリングと選択 (2000万行) | 2.85秒 | 0.22秒 | 3.2 GB | 0.5 GB |
| 複数カラムのグループ化集計 | 4.12秒 | 0.38秒 | 4.1 GB | 0.8 GB |
| 高カーディナリティ内部結合 | 6.50秒 | 0.75秒 | 5.8 GB | 1.1 GB |
| 遅延Parquetスキャン + フィルタリング | 3.20秒 | 0.08秒 | 2.4 GB | 0.1 GB |
Polarsは、グループ化集計をPandasよりも10倍以上高速に実行し、ピークRAMメモリの20%未満しか消費しませんでした。
# Benchmark Script snippet demonstrating high speed Polars aggregation
import time
import polars as pl
def benchmark_polars_group_by(df: pl.DataFrame) -> float:
start = time.perf_counter()
result = (
df.group_by(["category", "region"])
.agg([
pl.col("val1").mean().alias("avg_val1"),
pl.col("val2").max().alias("max_val2")
])
)
elapsed = time.perf_counter() - start
print(f"Polars GroupBy executed in {elapsed:.4f} seconds")
return elapsed
これらの経験的ベンチマークは、データ集約型アプリケーションが本番ETLパイプラインにPolarsをますます採用している理由を示しています。
実行速度に加えて、Polarsはワーカーのスレッド数がマルチコアプロセッサ全体でスケールしても高いパフォーマンス安定性を維持します。RustのRayonライブラリはスレッドのワークロードを動的にバランスさせ、複雑な結合操作中に単一のCPUコアがボトルネックになることを防ぎます。
さらに、Polarsは文字列操作(正規表現抽出や文字列分割パスなど)をPandasの文字列メソッドよりも最大15倍高速に処理するため、ログ分析パイプラインに最適です。
加えて、ベンチマーク分析により、Polarsのメモリ使用量は重い結合操作中も予測可能であり、共有サーバーインスタンスでのメモリ不足スパイクを防ぐことが確認されています。
さらに、ベンチマークテストでは、Polarsが32または64CPUコアのクラウドインスタンスで実行された場合でも線形パフォーマンススケーリングを維持するのに対し、Pandasはシングルスレッド評価の制約により頭打ちになることが示されています。
最後に、PolarsでのRAM消費量が少ないため、データエンジニアは共有サーバーインスタンスで複数の処理ジョブを同時に実行でき、メモリ枯渇によるクラッシュのリスクを回避できます。
こちらもおすすめ
- 私が毎日実際に使っているPythonのテクニック
- pyenv、Poetry、uv、pyproject.tomlを使ったモダンなPython開発環境
- 本番Pythonのための高度なMypy厳格モードパターン
- 高度なPytestフィクスチャとパラメータ化パターン
PolarsとPandasのパフォーマンスに関するよくある質問
Polarsは既存のデータサイエンスワークフローでPandasを完全に置き換えることができますか?
Polarsは分析DataFrame変換をPandasよりも高速に処理しますが、Pandasはscikit-learnのようなレガシーな機械学習ライブラリとのより広範な統合を維持しています。開発者は、特殊なMLパッケージと連携する際に、Polars DataFrameをNumPy配列またはArrowテーブルに変換できます。
PyArrowバックエンドを備えたPandas 2.0はPolarsのパフォーマンスと比較してどうですか?
Pandas 2.0ではオプションのPyArrowバックエンドストレージが導入され、元のNumPyブロックと比較してメモリ表現が改善されました。しかし、Pandasは即時シングルスレッド評価ループに拘束されているため、Polarsはマルチスレッド実行とクエリ最適化においてPandasを上回り続けています。
PolarsはDataFrame式と並行してSQLクエリ構文をサポートしていますか?
はい、Polarsには組み込みのSQLContextが含まれており、開発者はDataFrameまたはLazyFrameを登録し、Arrowメモリデータセットに対してANSI SQLクエリを直接実行できます。
Polarsの即時DataFrameとLazyFrameの主な違いは何ですか?
即時DataFrameは、操作をメモリ内で即座に評価し、各メソッド呼び出し後に変換されたテーブルを返します。LazyFrameは論理クエリプランを構築し、.collect()が呼び出されたときにコードを実行する前にPolarsエンジンが操作を最適化できるようにします。
Polarsは利用可能なCPUコア全体でマルチスレッドをどのように管理しますか?
PolarsはRustのRayonライブラリを使用して、利用可能なすべてのCPUスレッド間でデータ操作を自動的に並列化し、手動のマルチプロセッシングコードを不要にします。
すべてのPolars変換操作でメモリコピーのゼロコピーは保証されますか?
ゼロコピー実行は、スライス、カラム選択、名前変更操作中に発生します。基になるデータバイトを変更する操作(数学的な加算や文字列連結など)は、結果カラムのためにメモリを割り当てますが、Arrowの有効性マスクを効率的に使用します。
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

本番環境のSQLite: WALモード、高並行性、そして実践的なPRAGMA設定
高スループットな本番環境でSQLiteをマスターしましょう。先行書き込みログ(WAL)、busy_timeoutのチューニング、読み書きの同時実行性、そして実用的なベンチマークについて解説します。
Read more
グリーンソフトウェアエンジニアリング: 2026年のAIワークロードにおけるエネルギー効率の最適化
開発者向けに、CodeCarbon、量子化、インテリジェントバッチ処理、時間的ワークロードシフト、CIカーボンバジェットを活用し、計算負荷の高いAIおよびLLMワークロード全体のカーボンフットプリントと消費電力をプロファイリング、ベンチマーク、削減するための実用的な戦略を提供します。
Read more
PythonFastAPIを高並行処理向けに最適化する
Uvicorn、Gunicornワーカー、asyncパターン、データベースコネクションプーリングを網羅し、高並行処理環境におけるFastAPIアプリケーションのパフォーマンスを最大化するための詳細な解説。
Read more