Arrow C Data Interface — DataFrame interchange protocol
М10 урок 03 показал что Arrow — universal columnar format и pandas / Polars / DuckDB / Spark все используют те же memory buffers. Как это работает на практике? Через Arrow C Data Interface — ABI-stable C struct + PEP 749 (Python DataFrame interchange protocol). Этот урок объясняет mechanism zero-copy interop через language boundaries.
В этом уроке:
- Why universal Arrow representation matters — production pipelines combine multiple libraries.
- Arrow C Data Interface — ABI-stable C struct + reference counting.
- PEP 749 — Python DataFrame interchange protocol (in progress).
- Cross-library zero-copy demo — pandas ↔ Polars ↔ DuckDB ↔ Arrow Table.
- Cross-course — Storage Formats M07/03 IPC format + DataFusion M01/02 Arrow foundation.
Why universal Arrow representation matters
Production DE pipeline часто комбинирует несколько библиотек:
S3 Parquet → Polars (lazy ETL) → pandas (Jupyter analysis) → DuckDB (SQL) → ClickHouse (analytics)
Без universal representation каждый handoff требует:
Polars DataFrame → polars.write_parquet() → bytes → pandas.read_parquet() → pandas DataFrame
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
serialize CPU cost + 2x memory deserialize CPU cost + 2x memory
Multi-hop pipelines cost a lot of CPU и память на serialize-deserialize.
С universal Arrow representation:
Polars DataFrame ← (pointer) → Arrow C Data Interface → (pointer) → pandas DataFrame
Один и тот же memory accessed by multiple libraries — zero CPU cost, zero memory overhead. Это interchange protocol — не format на disk, а handshake между libraries в RAM.
Arrow C Data Interface — ABI-stable C struct
Arrow C Data Interface spec определяет три C struct:
struct ArrowSchema {
// Type metadata
const char* format;
const char* name;
const char* metadata;
int64_t flags;
int64_t n_children;
struct ArrowSchema** children;
struct ArrowSchema* dictionary;
// Reference counting / cleanup
void (*release)(struct ArrowSchema*);
void* private_data;
};
struct ArrowArray {
// Data buffers (validity bitmap, values, offsets)
int64_t length;
int64_t null_count;
int64_t offset;
int64_t n_buffers;
int64_t n_children;
const void** buffers;
struct ArrowArray** children;
struct ArrowArray* dictionary;
// Reference counting / cleanup
void (*release)(struct ArrowArray*);
void* private_data;
};
struct ArrowDeviceArray {
// GPU/accelerator-aware variant (for cuDF, etc.)
struct ArrowArray array;
int64_t device_id;
ArrowDeviceType device_type;
void* sync_event;
};
Ключевые свойства:
- ABI-stable — sizeof + layout struct гарантированы стабильны во времени. Library compiled на Arrow 5.0 читает struct из Arrow 15.0 без перекомпиляции.
- Language-agnostic — C struct работает в C, C++, Rust (через FFI), Python (через
ctypesилиcffi), R, Java (JNI). Universal handshake. - Reference counting — caller получает struct + увеличивает refcount; вызывает
release()при завершении. Producer manages lifecycle до последнего release. - Pointer-based — buffers — указатели на existing memory; никаких копий. Producer может вернуть указатель на свой internal buffer; consumer reads без копирования.
Cite Apache Arrow Format spec — C Data Interface.
PEP 749 — Python DataFrame interchange protocol
PEP 749 (in progress, status: deferred / draft) — стандартизирует Python-level DataFrame interchange:
# Любой DataFrame-подобный объект implements `__arrow_c_stream__` или `__arrow_c_array__` dunder
class MyDataFrame:
def __arrow_c_stream__(self, requested_schema=None):
"""Returns (Schema_capsule, Array_iterator_capsule)."""
...
def __arrow_c_array__(self, requested_schema=None):
"""Returns (Schema_capsule, Array_capsule)."""
...
# Consumers вызывают dunder; receive C Data Interface struct (wrapped в PyCapsule)
df = some_library_dataframe
schema_capsule, array_capsule = df.__arrow_c_array__()
# convert к Arrow Table / pandas / Polars
Стандарт описывает:
- Названия dunder methods (
__arrow_c_stream__,__arrow_c_array__,__arrow_c_schema__). - Ожидаемый PyCapsule API + naming convention.
- Schema negotiation (consumer может request specific types через
requested_schema).
Production status:
- pandas 2.2+ — implements producer side (DataFrame → Arrow capsule).
- Polars 1.0+ — implements both producer + consumer.
- PyArrow 15+ — implements full producer/consumer + schema negotiation.
- DuckDB 0.10+ — Arrow-compatible result format.
Pre-PEP 749 existed __dataframe__ protocol (DataFrame interchange protocol — older proposal). Production today — Arrow C Data Interface через PyCapsule поверх PEP 749 path.
Cite PEP 749 — Implementing Arrow PyCapsule Interface.
Cross-library zero-copy demo
import pyarrow as pa
import pandas as pd
import polars as pl
# Build Arrow Table once
table = pa.Table.from_pylist([{'a': 1, 'b': 'x'}, {'a': 2, 'b': 'y'}])
# Zero-copy → pandas (Arrow C Data Interface через PyArrow)
df_pd = table.to_pandas(types_mapper=pd.ArrowDtype)
# pandas 2.0+ Arrow-backed dtypes — same memory accessed
# Zero-copy → Polars
df_pl = pl.from_arrow(table)
# Polars Arrow backbone — direct pointer
# Zero-copy → DuckDB
import duckdb
con = duckdb.connect()
con.register('arrow_view', table)
result = con.execute("SELECT * FROM arrow_view WHERE a > 1").arrow()
# Result back as Arrow Table — same RAM
# Round-trip pandas → Polars без serialize
arrow_from_pd = pa.Table.from_pandas(df_pd) # zero-copy if Arrow-backed
back_to_polars = pl.from_arrow(arrow_from_pd) # zero-copy
Implementation detail: под капотом каждый из этих converters вызывает Arrow C Data Interface dunder methods (PEP 749 path) или PyArrow native APIs. Consumer получает PyCapsule с ABI-stable struct → reads buffers → no copy.
Production rule: в multi-library pipelines избегайте pickle.dumps, df.to_csv(), df.to_json() для in-memory handoffs — используйте Arrow путь (pa.Table.from_pandas(df), pl.from_arrow(table), con.register('view', table)). Это существенно дешевле для больших данных.
Cite PyArrow docs — Pandas integration + Polars docs — Arrow integration.
Cross-course → Storage Formats M07/03 IPC + DataFusion M01/02 Arrow foundation
Storage Formats M07 arrow — Arrow on-disk + on-wire counterpart:
- 07/03 — IPC format — Arrow IPC stream/file format spec — wire layout для cross-process zero-copy. C Data Interface работает в RAM; IPC format кодирует то же самое в bytes для disk persistence или network transfer (Flight protocol).
- 07/05 — Flight protocol — gRPC-based Arrow data transfer protocol; production pattern для cross-server zero-copy (Spark Connect, BigQuery Storage API).
Cross-course connection: C Data Interface = in-RAM handshake; IPC format = bytes-on-wire equivalent. Same buffers, two delivery mechanisms.
DataFusion M01 arrow-foundation — DataFusion (Rust-native query engine) использует Apache Arrow как native representation:
- 01/02 — Arrow memory layout — Arrow memory model в Rust (
arrow-rscrate). Same spec как PyArrow — ABI-stable между Rust ↔ Python через C Data Interface. - 02 — DataFusion architecture — engine integration с Arrow.
Cross-course bridge insight: Arrow C Data Interface — glue который соединяет multi-language ecosystem. pandas (Python) → Polars (Rust внутри Python) → DuckDB (C++ внутри Python) → Spark (JVM) → DataFusion (Rust). Все читают/пишут same Arrow buffers через C Data Interface.
Что в следующем уроке
М10 урок 05 — Pyodide constraints — почему pandas / Polars / PyArrow / NumPy не запускаются в browser challenges; WASM size limits, C-extension dependencies, micropip experimental status. Объясняет decision учить эти libraries conceptually + Run-on-Your-Machine для real demos.