Skill Tracer
Skills › Scientific Agent Skills › vaex › performance.md

performance.md

114 lines · skills/vaex/references/performance.md at 5f2bc03

Open on GitHub
1# Performance and memory
2
3Targets core 4.19.0. See [caching](https://vaex.io/docs/guides/caching.html),
4[configuration](https://vaex.io/docs/conf.html), and released executor/cache source.
5

Performance and memory

Targets core 4.19.0. See caching, configuration, and released executor/cache source.

Raw lines 1 to 5
6## Batch independent reductions
7
8```python
9import asyncio
10import numpy as np
11import vaex
13df = vaex.from_arrays(x=np.arange(10.), y=np.arange(10.) ** 2)
14df['derived'] = df.x ** 2 + df.y
15pending = [df.derived.mean(delay=True), df.x.std(delay=True), df.y.sum(delay=True)]
16df.execute()
17values = [task.get().item() for task in pending]
18assert np.allclose(values, [57., np.std(np.arange(10.)), 285.])
20async def compute(frame):
21 mean = frame.x.mean(delay=True)
22 count = frame.count(delay=True)
23 await frame.execute_async()
24 return mean.get().item(), count.get().item()
26assert asyncio.run(compute(df)) == (4.5, 10)
27```
29In a notebook with a running event loop, use `await compute(df)` instead of
30`asyncio.run`. Defining an expression or virtual column defers per-row evaluation;
31ordinary reductions execute now. Promises must be executed before `.get()`.
32There is no public `vaex.execute([tasks])` collection API. Batching compatible tasks
33can share scans, but "one pass" is not universal: limits, means, variance or dependant
34tasks can need preliminary work. Measure actual data passes and timing on the target workload.

Batch independent reductions

import asyncio
import numpy as np
import vaex

df = vaex.from_arrays(x=np.arange(10.), y=np.arange(10.) ** 2)
df['derived'] = df.x ** 2 + df.y
pending = [df.derived.mean(delay=True), df.x.std(delay=True), df.y.sum(delay=True)]
df.execute()
values = [task.get().item() for task in pending]
assert np.allclose(values, [57., np.std(np.arange(10.)), 285.])

async def compute(frame):
    mean = frame.x.mean(delay=True)
    count = frame.count(delay=True)
    await frame.execute_async()
    return mean.get().item(), count.get().item()

assert asyncio.run(compute(df)) == (4.5, 10)

In a notebook with a running event loop, use await compute(df) instead of asyncio.run. Defining an expression or virtual column defers per-row evaluation; ordinary reductions execute now. Promises must be executed before .get(). There is no public vaex.execute([tasks]) collection API. Batching compatible tasks can share scans, but "one pass" is not universal: limits, means, variance or dependant tasks can need preliminary work. Measure actual data passes and timing on the target workload.

Raw lines 6 to 35
36## Memory boundaries
38| Operation | Allocation to plan for |
39| --- | --- |
40| HDF5/Arrow open | Metadata, handles, mapped/address space and accessed pages |
41| Virtual columns | Expression metadata and per-chunk temporary arrays |
42| Filters/selections | Boolean masks and selection/cache metadata |
43| Reductions/grids | Chunk buffers and grid accumulators; grid size grows with dimensions |
44| Groupby/unique/join/sort | Cardinality dictionaries, output tables and/or row index arrays |
45| `.values`, `.to_numpy()`, unchunked pandas conversion | Requested full arrays |
46| `.materialize()` | Full selected virtual columns in memory |
47| sklearn `Predictor.fit` | Full feature matrix and target, plus estimator allocations |
49`df.byte_size()` describes data bytes, not resident memory or a process limit.
50`df.is_local()` tells whether the frame is local rather than a remote server frame;
51it accepts no column argument and does not indicate virtual columns or memory mapping.
52Use `df.virtual_columns` to inspect derived definitions. `df.copy()` is shallow.
54```python
55assert 'derived' in df.virtual_columns
56assert df.is_local()
57materialized = df.materialize('derived') # small fixture only
58assert 'derived' not in materialized.virtual_columns
59assert np.allclose(materialized.derived.to_numpy(), 2 * np.arange(10.) ** 2)
60rows = 0
61for start, stop, chunk in df[['x', 'derived']].to_pandas_df(chunk_size=3):
62 assert len(chunk) == stop - start
63 rows += len(chunk)
64assert rows == 10
65```
67Export already evaluates virtual columns in chunks. Materializing first is not a fix
68for an out-of-memory export. Reduce selected columns/chunk size, bound group cardinality,
69check disk capacity and profile the actual operation instead. Avoid collecting all
70pandas chunks into a list, which reconstructs the full allocation.

Memory boundaries

OperationAllocation to plan for
HDF5/Arrow openMetadata, handles, mapped/address space and accessed pages
Virtual columnsExpression metadata and per-chunk temporary arrays
Filters/selectionsBoolean masks and selection/cache metadata
Reductions/gridsChunk buffers and grid accumulators; grid size grows with dimensions
Groupby/unique/join/sortCardinality dictionaries, output tables and/or row index arrays
.values, .to_numpy(), unchunked pandas conversionRequested full arrays
.materialize()Full selected virtual columns in memory
sklearn Predictor.fitFull feature matrix and target, plus estimator allocations

df.byte_size() describes data bytes, not resident memory or a process limit. df.is_local() tells whether the frame is local rather than a remote server frame; it accepts no column argument and does not indicate virtual columns or memory mapping. Use df.virtual_columns to inspect derived definitions. df.copy() is shallow.

assert 'derived' in df.virtual_columns
assert df.is_local()
materialized = df.materialize('derived')  # small fixture only
assert 'derived' not in materialized.virtual_columns
assert np.allclose(materialized.derived.to_numpy(), 2 * np.arange(10.) ** 2)
rows = 0
for start, stop, chunk in df[['x', 'derived']].to_pandas_df(chunk_size=3):
    assert len(chunk) == stop - start
    rows += len(chunk)
assert rows == 10

Export already evaluates virtual columns in chunks. Materializing first is not a fix for an out-of-memory export. Reduce selected columns/chunk size, bound group cardinality, check disk capacity and profile the actual operation instead. Avoid collecting all pandas chunks into a list, which reconstructs the full allocation.

Raw lines 36 to 71
72## Explicit caching and timing
74`vaex.cache` starts disabled unless configured. A repeated mean is not guaranteed to
75be cached. Optional `cachetools` supplies the bounded in-memory cache below:
77```python
78import time
79import vaex.cache
81with vaex.cache.memory(maxsize='8MiB', clear=True):
82 first = df.x.mean()
83 second = df.x.mean()
84 assert first == second
85with vaex.cache.off():
86 start = time.perf_counter()
87 result = df.y.sum()
88 elapsed = time.perf_counter() - start
89assert result == 285
90print({'elapsed_seconds': elapsed, 'column_bytes': df.byte_size()})
91```
93Cache budget is not a total-RSS cap. Cached results, operating-system file cache and
94warm compiled kernels can change timing; report warm/cold conditions, package versions,
95row/column counts, dtype, hardware, storage and workload. No universal billion-rows-per-second
96claim applies to arbitrary expressions or files. Use Python profiling tools or timing;
97`with vaex.profiler():` is not a supported context manager.
99Set `VAEX_NUM_THREADS` in the process environment before starting Python for repeatable
100thread limits. Avoid oversubscription with BLAS/Numba/estimator threads. More threads
101can increase per-worker buffers and do not guarantee faster I/O.

Explicit caching and timing

vaex.cache starts disabled unless configured. A repeated mean is not guaranteed to be cached. Optional cachetools supplies the bounded in-memory cache below:

import time
import vaex.cache

with vaex.cache.memory(maxsize='8MiB', clear=True):
    first = df.x.mean()
    second = df.x.mean()
    assert first == second
with vaex.cache.off():
    start = time.perf_counter()
    result = df.y.sum()
    elapsed = time.perf_counter() - start
assert result == 285
print({'elapsed_seconds': elapsed, 'column_bytes': df.byte_size()})

Cache budget is not a total-RSS cap. Cached results, operating-system file cache and warm compiled kernels can change timing; report warm/cold conditions, package versions, row/column counts, dtype, hardware, storage and workload. No universal billion-rows-per-second claim applies to arbitrary expressions or files. Use Python profiling tools or timing; with vaex.profiler(): is not a supported context manager.

Set VAEX_NUM_THREADS in the process environment before starting Python for repeatable thread limits. Avoid oversubscription with BLAS/Numba/estimator threads. More threads can increase per-worker buffers and do not guarantee faster I/O.

Raw lines 72 to 102
103## Other execution engines
105Core 4.19.0 does not have `to_dask_dataframe()`. Its documented Dask integration is
106`to_dask_array()` for numerical arrays; that alone does not provision a distributed
107cluster. Core pins Dask `<2024.9`, so current standalone Dask recommendations cannot
108be transplanted into this environment without compatibility work. [Dask guide](https://vaex.io/docs/guides/dask.html).
110For custom functions, prefer built-in expression operations. `df.apply(function,
111arguments=[...], vectorize=True)` calls the function on array chunks and can allocate
112chunk outputs; `vectorize=False` has different, row-function behavior. Verify types,
113nulls and independent numerical results. JIT compilation does not create a nonexistent
114`Expression.custom_agg` API, and compilation time is separate from steady-state timing.

Other execution engines

Core 4.19.0 does not have to_dask_dataframe(). Its documented Dask integration is to_dask_array() for numerical arrays; that alone does not provision a distributed cluster. Core pins Dask <2024.9, so current standalone Dask recommendations cannot be transplanted into this environment without compatibility work. Dask guide.

For custom functions, prefer built-in expression operations. df.apply(function, arguments=[...], vectorize=True) calls the function on array chunks and can allocate chunk outputs; vectorize=False has different, row-function behavior. Verify types, nulls and independent numerical results. JIT compilation does not create a nonexistent Expression.custom_agg API, and compilation time is separate from steady-state timing.

Raw lines 103 to 114