2026年になってもC++でCSVを処理したくなる機会は思いのほか多いです。 ログ解析、バッチ変換、設定読み込み……。 個人的には最近業務が変わって膨大なアイテム情報が記載されたCSVファイルの処理に直面してたりします。
こんな時にライブラリを選ぼうとすると「どれが速いのか」「multilineに対応しているか」「余分な列はどう扱うか」といった疑問が次々と出てきます。
そこで2026年現在の主要C++ CSVライブラリを整理して vincentlaucsb/csv-parser と stephenberry/glaze の2択に絞り込み、実測ベンチマークを通じてユースケースごとの使い分けを示します。
C++でCSVを扱う際の要件
今回のライブラリ選定は以下の4要件を満たすことを基準としました。
- 単スレッドでも高速に動かしたい マルチスレッドが使えない環境(Emscripten/WASM等)や他処理の負荷が高い環境で安定して高速動作させたい
- multiline(フィールド内改行)に対応したい
- ヘッダなしCSVに後から列名を付与したい
- 指定列以降の余り列の不一致や過剰をエラーにせず無視したい
主要ライブラリの比較
2026年時点で開発が継続されているように見える主要C++ CSVライブラリを整理します。
| ライブラリ | バージョン | 言語標準 | 配布形態 | ライセンス | 特徴 |
|---|---|---|---|---|---|
| vincentlaucsb/csv-parser | v5.3.0 (2026-05) 活発 | C++11-20 | CMake / single_header | MIT | RFC4180完全準拠、multiline、SIMD+mmap、大規模ETL向け |
| ben-strasser/fast-cpp-csv-parser | 2025年が最終更新 | C++11 | 単一ヘッダ | BSD-3 | 枯れて安定、I/Oとparse重畳 |
| d99kris/rapidcsv | 活発 | C++11 | 単一ヘッダ | BSD-3 | 読み書き両対応、APIが最も簡潔 |
| p-ranav/csv2 | 活発 | C++11 | 単一ヘッダ | MIT | mmap+lazy、高速 |
| ashtum/lazycsv | 活発 | C++17 | 単一ヘッダ | MIT | 真のlazy、ゼロアロケーション |
| stephenberry/glaze | v8.3.0 (2026-08) 活発 | C++23 | ヘッダオンリー | MIT | 構造体反射でCSV/JSONを統一API |
| Apache Arrow CSV | v25 活発 | C++17 | ライブラリ | Apache-2.0 | Arrow Tableへ直接ingest、列指向パイプライン |
ざっくり用途別に整理すると以下のような感じになると思います。
- 手軽に読み書きしたい →
rapidcsv - シングルヘッダファイルでで依存ゼロ →
fast-cpp-csv-parser/csv2/lazycsv - struct ↔ CSV/JSON を統一API →
Glaze - データパイプラインの中で数GBを高速処理したい →
vincentlaucsb/csv-parser - Arrow/Parquetと連携 →
Arrow CSV
なぜ vincentlaucsb と Glaze の2択か
今回最優先にした条件は「高速で動作するCSV読み書き」です。
rapidcsv はAPIが最も簡潔ですが全行をメモリに展開するため、大きなファイルでは遅くなります。
fast-cpp-csv-parser・csv2・lazycsvはシングルヘッダファイルで簡単に使えて高速ですが、multiline非対応やヘッダ後付け・余り列の制御が弱いといった制約があります。
Arrow CSVはそもそもの目的が高速処理を行うことにあるためスループットは申し分ないものの、依存が巨大で単純なCSV処理のためだけに導入するには過剰です。
速度と機能の両面から絞り込んだ結果、vincentlaucsb/csv-parser(機能の網羅性)とstephenberry/glaze(リフレクションベースの型安全なAPI)の2択となりました。
この2つを実測ベンチマークで比較することにしました。
結果を先に
もし単スレッドでの処理を想定するならば、もしくは30MB以下のファイルサイズであれば、Glazeがvincentlaucsb/csv-parserよりも高速に動作する可能性が高いです。
逆にこの条件だけがGlazeの優位性が示せるパターンになります。
余り列が存在する場合には、Glazeでは制御は難しく何もしないと失敗してしまいます。
巨大ファイルに対しては、並列処理がライブラリ内でvincentlaucsb/csv-parserの方が安定して高速に処理できます。
ライブラリの特徴
上で挙げた4要件を持ち込むと、各ライブラリの対応状況がはっきりします。
vincentlaucsb/csv-parser
機能面で最も充実しています。
VariableColumnPolicyによる余り列制御no_header().column_names()によるヘッダ後付けthreading(false)によるスレッド動作の明示的な制御
今年にはいって2年ぶりぐらいに開発が再開して、大規模データの高速処理に力を入れて精力的に開発をしているのが印象的です。
具体的には次のような組み合わせのアプローチが取られています。
- mmap でファイル読み込み
- SIMD命令の多用
- 並列処理の導入
- ファイルサイズが50MBを超えるとデータ分割に効率的に対応できる投機的並列処理に移行 ref
本家README.mdの主張では、warm cacheの条件下では秒間1GByte以上の処理をCore i5-12400で実現可能としているようです。ref 秒間1Gbyteの読み込み速度はすごいですね、warm cacheでなければI/Oボトルネックになってしまう場合が多くなりそうです。
Glaze
C++23まで機能を活かしてリフレクションもどきを実現したJSONライブラリですが、実はCSVにも対応しています。 構造体(正確には集成体)とCSV/JSON/YAML/TOMLなどを統一的に扱える点が魅力です。
JSONライブラリとして長年培った技術とノウハウをCSV処理にも再利用できるためSIMD命令の利用は最低限ですが、文字列操作や型変換の最適化が効いていて、単スレッドでの速度で優位に立ちます。
ただしリフレクションベースであることが足枷になり列数が構造体フィールド数と一致しない場合に「unknown_key エラーで失敗する」という制約があります。
※ JSONでは余りのキーが存在しても無視する設定ができるのですが、CSVでは未サポートのようです。
ベンチマーク環境と方法
それでは実際にベンチマークを行ってみます。
メインPCはRyzen7 7700の環境があるのですが開発で忙しいのでサブマシンでの計測となります。ご了承ください。
| 条件 | 詳細 |
|---|---|
| CPU | Ryzen 7 1700 |
| コンパイラ | GCC 16.2.1、Release -O2 -march=native |
| 測定方法 | 同一CSVを best of 5 (warmup 1)、ファイルI/O込み |
| vincentlaucsb/csv-parserの設定 | threading(false) 固定、variable_columns(KEEP) で余り列許容 |
| Glazeの設定 | glz::read<opts_csv{.layout=colwise}>(ColWise, content)(3列構造体) |
| データ | clean(引用符なし)/ quoted(引用符あり)/ multiline(フィールド内改行)の3種類を混在 |
速度比較:単スレッドではGlazeが速い
200k行・500k行(3列)
| データセット | vincent | glaze | 倍率 |
|---|---|---|---|
| clean_200k (4.7MB) | 66.7ms | 29.1ms | 2.29x |
| quoted_200k (6.6MB) | 68.3ms | 41.6ms | 1.64x |
| multiline_200k (4.7MB) | 66.9ms | 26.6ms | 2.51x |
| clean_500k (12MB) | 165.9ms | 63.3ms | 2.62x |
| quoted_500k (17MB) | 171.1ms | 127.5ms | 1.34x |
| multiline_500k (12MB) | 165.7ms | 64.3ms | 2.58x |
Glazeはクオート付きのフィールドが含まれるCSVは若干苦手なようですが、それでも vincentlaucsb/csv-parser よりも優位に立つことが多いです。
1MB程度の小さなデータ
もうすこし小さなデータでも確認してみます。
| データセット | vincent | glaze | 倍率 | 備考 |
|---|---|---|---|---|
| clean_1MB (43k行) | 16.3ms | 6.3ms | 2.59x | |
| quoted_1MB (30k行) | 12.3ms | 5.8ms | 2.12x | |
| multiline_1MB (43k行) | 16.8ms | 6.8ms | 2.45x | |
| 6列CSV(余り列あり) | 11-12ms 成功 | fail unknown_key |
— | glazeは失敗 |
やはり単スレッドではGlazeが1.3〜2.6倍速いという結果になりました。
ただし最後の行が重要で、余り列があるとGlazeは失敗してしまうのです。
vincentlaucsb/csv-parserはVariableColumnPolicy::KEEPで余り列を無視することができるのです。
一般論として「vincentlaucsb/csv-parserは最速である」という記述がおおく、コーディングエージェントに尋ねてもこの結果になることが多いのですが、これはthreading(true)による並列処理時の話です。単スレッドrow-iterationでは逆転しています。
スレッド有効化の分岐点は50MB
vincentlaucsb/csv-parser は threading(true) でSPSC + 投機的並列が有効になります。
これを実際に体感するため、1MB〜80MBで計測してみました。
| ファイルサイズ | clean ON | clean OFF | 倍率 | quoted ON | quoted OFF | 倍率 |
|---|---|---|---|---|---|---|
| 1 MB | 28.5 ms | 17.1 ms | 0.60 | 26.7 ms | 12.4 ms | 0.47 |
| 5 MB | 157 ms | 87.6 ms | 0.56 | 108 ms | 53 ms | 0.49 |
| 10 MB | 310 ms | 173 ms | 0.56 | 337 ms | 102 ms | 0.30 |
| 30 MB | 1046 ms | 374 ms | 0.36 | 764 ms | 270 ms | 0.35 |
| 50 MB | 429 ms | 744 ms | 1.73 | 1342 ms | 530 ms | 0.40 |
| 80 MB | 687 ms | 1185 ms | 1.72 | 463 ms** | 847 ms | 1.83 |
綺麗に50MBで処理速度が変わりました。
- 50MB未満 :
threading(false)として単一スレッドにした方が速いです。スレッド間のキュー同期がオーバーヘッドになるようです。 - 50MB以上 : 投機的並列が機能し始め、cleanでは1.73倍、quotedは80MBで1.83倍の高速化が得られます。
機能比較:余り列の扱いが決め手
| 要件 | vincentlaucsb/csv-parser | Glaze |
|---|---|---|
| 単スレッド | ○ threading(false) |
○ |
| multiline(RFC4180) | ○ デフォルト有効 | ○ colwiseで可 |
| ヘッダ後付け | ○ no_header().column_names(...) |
△ use_headers=falseで位置依存 |
| 余り列を無視(エラーにしない) | ○ VariableColumnPolicy::KEEP |
× unknown_keyで失敗 |
| 行数・バイトオフセット取得 | ○ n_rows() / byte_offset() |
× vector.size()のみ |
ユースケース別の使い分け
要件4点すべて必要 → vincentlaucsb/csv-parser
#include "csv.hpp" using namespace csv; CSVFormat fmt; fmt.threading(false) .no_header() .column_names({"id", "value", "name"}) // ヘッダ後付け .variable_columns(VariableColumnPolicy::KEEP); // 余り列を捨てる CSVReader reader("data.csv", fmt); for (auto& row : reader) { auto id = row["id"].get<int>(); auto value = row["value"].get<double>(); auto name = row["name"].get<std::string>(); } size_t total = reader.n_rows(); // 処理済み行数
struct と CSV を 1 対 1 対応させたい → Glaze
列数が構造体フィールド数と一致する場合、Glazeは擬似的なリフレクションにより最もシンプルに書けます。
また単スレッドでは vincentlaucsb/csv-parser より1.3〜2.6倍速いです。
#include "glaze/glaze.hpp" struct Record { int id; double value; std::string name; }; std::vector<Record> records; glz::read<glz::opts_csv{.layout = glz::colwise}>(records, csv_content);
数GBのETL → vincentlaucsb + threading(true)
50MB超ならthreading(true)で投機的並列が有効になる vincentlaucsb/csv-parser が高速になります。
DataFrameやchunk_parallel_applyでETLパイプラインも組めます。
手軽に読み書きしたいだけ → rapidcsv
rapidcsv::Document doc("data.csv"); auto col = doc.GetColumn<double>("value");
APIが最も簡潔です。ただし全行メモリ展開のため5MB超では遅くなります。
まとめ
| シナリオ | 推奨 |
|---|---|
| 余り列あり / ヘッダ後付け / multiline | vincentlaucsb/csv-parser |
| struct ↔ CSV、列数一致、速度優先 | Glaze |
| 50MB超のETL | vincentlaucsb/csv-parser + threading(true) |
| 小規模、手軽に読み書き | rapidcsv |
| 依存ゼロ、シングルヘッダで手軽に導入 | fast-cpp / csv2 / lazycsv |
速度だけを見るとGlazeが単スレッドで約2倍速いです。
ちょっとしたデータ処理であればGlazeで十分ですが、クレンジングが必要なゴミが多いデータや大規模データのETLにはvincentlaucsb/csv-parserが適しています。
個人的には、JSON/CSV/YAML/TOMLなどのデータ形式ではほぼ最初にGlazeを検討するぐらいGlaze推しなので、納得の行く結果になりました。ref
一方今年に入って開発が再開された vincentlaucsb/csv-parser の強みも明確になったのが収穫でした。
今もマルチスレッド対応や大規模データのETL性能向上など着実に進化しているので、今後も注目していきたいと思います。
枯れたジャンルと思っていましたが、C++のCSVライブラリにはまだ発展の余地があるみたいで楽しめそうです。


