Skip to content

Introduce Menudet 0.1 draft for portable array and table computation - #741

Merged
FrancescAlted merged 303 commits into
mainfrom
improved-miniexpr
Nov 6, 2026
Merged

FrancescAlted merged 303 commits into
mainfrom
improved-miniexpr

Conversation

@FrancescAlted

@FrancescAlted FrancescAlted commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Supersedes #735.

Menudet is a little language for portable computation on arrays and tables, with Python-like syntax and native execution through miniexpr. Validated kernels can be saved and loaded without reconstructing Python functions, while preserving defined numeric semantics and logical computation groups. Menudet 1.0 remains a draft specification.

Main changes

  • Add standalone, validated kernel artifacts, @blosc2.menudet authoring with annotated or inferred dtypes, and native execution without Python reconstruction or execution fallback.
  • Define NumPy-style numerical semantics, including wrapping array integers, checked weak-scalar conversions, operand-driven floating-point types, and interpreted complex arithmetic. Support control flow, fixed-width strings, logical coordinates and ordered block reductions.
  • Add native expression and staged graph execution, parallel resident maps/reductions, and bounded readers for direct computation over compressed operands.
  • Remove NumExpr as a runtime dependency. Automatically interpret single-return expressions and prefer JIT for multi-statement programs, while honoring explicit execution settings. Unsupported optional JIT falls back before execution; execution failures propagate without replaying numerical work.
  • Improve NumPy-output evaluation with explicit output buffers, layout/casting handling, reusable evaluators and operand-free preparation caches. Reduce cold overhead through shared syntax analysis, prepared-plan reuse, fewer Menudet artifact imports and native uniform-cast reuse.
  • Execute supported DSL-backed LazyUDFs and array constructors through Menudet. Preserve logical reduction groups through persistence, partial reads and output rechunking.
  • Integrate portable persistence across LazyUDFs, frames, MessagePack and object stores. Restore validated numerical LazyExpr recipes under safe loading; retain explicit deserialize="full" for legacy Python-specific recipes and reject unsupported saves before destination writes.
  • Add CTable computed/generated transformers with explicit independent-row semantics, and regression coverage for pandas-free Arrow dictionary imports.
  • Expose explicit math-library and floating-point policies, update the pinned native dependency, and expand native/WASM qualification, documentation, examples and benchmarks.
  • Retain independent math certification with 284 samples across 40 functions, float32 and float64; these are finite-corpus checks, not universal accuracy bounds.

Performance and validation

For x + (y - 1) over 100K float64 NumPy operands on macOS arm64, with 12 configured threads and fresh NumPy outputs, preparation-cold medians from 151 samples were:

Engine Median
numexpr (comparison only) 210 µs
LazyExpr-NumPy 168 µs
evaluate 186 µs
Menudet 177 µs

Preparation/compiler caches were cleared before each cold sample; inputs were prebuilt and CPU/OS caches were not flushed. Measurements used a rebuilt native extension containing the pinned uniform-cast optimization. Compressed-output LazyExpr is excluded from this comparison.

  • Full local Python suite after merging current main: 11,796 passed, 87 skipped, including a subprocess that blocks pandas imports and the unsigned dictionary overflow regression.
  • Native uniform-cast parity tests and 15 additional native graph/portable checks passed.
  • Ruff, formatting and applicable commit hooks passed. Heavy/network tests and release-wheel qualification were not rerun for these final changes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Active issues remain, including a critical dispatch flag that prevents supported native Menudet calls from reaching compilation, a missing CMake header dependency that can leave generated bindings stale, and inaccurate performance-record metadata.

3 open issues
What changed in this PR

This pull request introduces Menudet 0.1 as a portable, validated computation and persistence layer for arrays and tables. It adds native artifact execution, replaces runtime NumExpr integration, and expands persistence, qualification, documentation, examples, and regression coverage.

Changes:

  • Adds Menudet authoring, specialization, native execution, graph support, numerical policies, and portable persistence.
  • Updates LazyUDFs, table transformations, remote storage, deserialization, and legacy DSL migration behavior.
  • Adds native qualification tooling, benchmarks, CI workflows, WASM coverage, documentation, and examples.
File Summary Review note
tools/​menudet_batch_qualification.py Adds cross-compiler Menudet corpus qualification. Moderate: Add an unversioned gcc fallback; the current default only probes gcc-16. (1 vote)
tests/​test_tip11_hash.py Tests portable integer hash behavior. —
tests/​test_remote_store_cache_locking.py Tests Windows cache-lock contention. —
tests/​test_remote_caterva2.py Gates rich Caterva2 tests on NumPy string support. Moderate: Safely guard access to np.dtypes for NumPy 1.26. (1 vote)
tests/​test_releasegil_isolation.py Verifies release-GIL fixture isolation. —
tests/​test_pytest_configuration.py Tests pytest without its cache provider. —
tests/​test_orthogonal_cases.py Tests deterministic orthogonal sampling. —
tests/​test_objectarray.py Updates Menudet object persistence assertions. —
tests/​test_numpy_boolean_fallback.py Tests scalar and array boolean fallback semantics. —
tests/​test_native_qualification_evidence.py Validates native qualification evidence. —
tests/​test_menudet_math_vectors.py Verifies numerical certification vectors. —
tests/​test_menudet_inverse_names.py Tests canonical native math-function names. —
tests/​test_menudet_descriptor_host.py Tests native descriptor routing. —
tests/​test_menudet_backend.py Tests Menudet backend and execution policies. —
tests/​test_locking.py Limits worker-thread creation in locking tests. —
tests/​test_http_server_diagnostics.py Tests HTTP error diagnostics. —
tests/​test_expression_threads.py Tests expression-thread isolation. Moderate: Propagate child interpreter assertion and import failures. (1 vote)
tests/​test_deserialization.py Tests safe LazyExpr persistence and loading. —
tests/​test_compressors.py Updates compressor test coverage. —
tests/​test_chunk_reader_lifetime.py Tests nested persisted-reader lifetime management. —
tests/​test_cache_lock.py Updates Windows cache-lock expectations. —
tests/​test_bench_sum_linear_idx.py Tests Menudet coordinate and reduction benchmarks. —
tests/​test_bench_linear_constructor.py Tests Menudet linear constructor benchmarks. —
tests/​test_bench_jit_dsl.py Tests JIT benchmark captures and outputs. —
tests/​test_bench_evaluator_fallbacks.py Validates evaluator fallback diagnostics. —
tests/​test_bench_evaluate_numpy_parity.py Tests NumPy-output parity qualification. —
tests/​test_bench_compute_expr_udf.py Tests predicate benchmark bindings. —
tests/​test_b2nd_to_zarr.py Adjusts Zarr warning handling. —
tests/​ndarray/​test_wasm_dsl_jit.py Updates WASM kernel fixtures. —
tests/​ndarray/​test_stringarrays.py Reduces representative string-array combinations. —
tests/​ndarray/​test_string_output.py Updates string-kernel fixtures. —
tests/​ndarray/​test_setitem_chunkwise.py Adjusts NumPy-version-specific assignment expectations. —
tests/​ndarray/​test_safer_jit.py Expands safer-JIT workload and cache checks. —
tests/​ndarray/​test_math_backend.py Tests per-call math backend selection. —
tests/​ndarray/​test_lazyudf.py Updates LazyUDF persistence coverage. —
tests/​ndarray/​test_jit_dsl_dispatch.py Updates Menudet control-flow dispatch expectations. —
tests/​ndarray/​test_expression_fusion_threads.py Tests thread-local fusion state. —
tests/​ndarray/​test_elementwise_funcs.py Samples elementwise function and layout combinations. —
tests/​ndarray/​test_dsl_js.py Updates JavaScript-transpiler fixtures. —
tests/​ndarray/​test_dsl_comparisons.py Uses source normalization for comparisons. —
tests/​ndarray/​test_auto_parts.py Uses orthogonal sampling for storage parts. —
tests/​ndarray/​safer_jit_probe.py Adds a focused safer-JIT workload. —
tests/​ctable/​test_utf8.py Updates UTF-8 Menudet fixtures. —
tests/​ctable/​test_pandas_arrow_import.py Gates UTF-8 Arrow imports on NumPy support. Moderate: Guard the absent np.dtypes namespace before accessing StringDType. (1 vote)
tests/​ctable/​test_null_storage_schema.py Tests UTF-8 behavior without StringDType. —
tests/​ctable/​test_null_channel.py Tests null predicates during equality suppression. —
tests/​ctable/​test_groupby.py Updates groupby kernel fixtures. —
tests/​ctable/​test_dictionary_column.py Adds pandas-free dictionary import coverage. —
tests/​ctable/​test_ctable_apply.py Updates CTable apply fixtures. —
tests/​ctable/​test_arrow_interop.py Updates DuckDB result-fetching compatibility. —
src/​blosc2/​zarr_source.py Preserves accounting through Zarr read-only wrappers. Moderate: Preserve the metadata mapping when creating CountingStore. (1 vote)
src/​blosc2/​version.py Bumps the development version to 4.15.0. —
src/​blosc2/​tree_store.py Adds persistence preflight validation. —
src/​blosc2/​schunk.py Enforces Menudet and legacy deserialization policies. —
src/​blosc2/​remote_store_cache.py Adds Windows cache-lock retry behavior. —
src/​blosc2/​remote_parquet.py Records original Parquet storage provenance. —
src/​blosc2/​remote_array.py Records remote-array expression provenance. Moderate: Update expression provenance when RemoteArray.refresh() replaces its source. (1 vote)
src/​blosc2/​proxy.py Routes DSL-shaped JIT wrappers through Menudet. Critical: Avoid setting the compatibility flag so that supported native control-flow calls are rejected before Menudet dispatch. (1 vote)
src/​blosc2/​objectarray.py Updates Menudet persistence documentation. —
src/​blosc2/​jit_config.py Adds scoped math backend options. —
src/​blosc2/​indexing.py Migrates indexed evaluation to the typed evaluator. —
src/​blosc2/​groupby.py Uses source-authored kernel normalization. —
src/​blosc2/​exceptions.py Adds legacy-recipe and deserialization errors. —
src/​blosc2/​embed_store.py Adds persistence preflight validation. —
src/​blosc2/​dsl_js.py Clarifies internal source-kernel transpilation. —
src/​blosc2/​dsl_array_bridge.h Adds native array artifact and reader bridges. —
src/​blosc2/​dict_store.py Adds lazy-size estimation and persistence preflight. —
src/​blosc2/​deserialization.py Handles objects without physical carriers. —
src/​blosc2/​ctable_storage.py Tracks logical dtype overrides. —
src/​blosc2/​ctable_nulls.py Keeps sentinel predicates lazy during fusion. —
src/​blosc2/​ctable_indexing.py Restricts summary bitmap projection to sound conjunctions. —
src/​blosc2/​core.py Removes NumExpr integration and rejects legacy recipes. —
src/​blosc2/​cli/​zarr_to_b2nd.py Supports enum-valued codec and filter options. —
src/​blosc2/​batch_array.py Updates Menudet persistence documentation. —
src/​blosc2/​_utf8_array.py Renames UTF-8 evaluator options. —
src/​blosc2/​__init__.py Exposes Menudet APIs and removes NumExpr initialization. —
scripts/​verify_safe_lazyexpr.py Records bounded safe-graph qualification evidence. —
scripts/​verify_native_qualification.py Verifies exact native revision qualification. —
scripts/​qualify_without_numexpr.py Provides NumExpr-free qualification execution. —
pytest.ini Registers native semantics and warning configuration. —
pyproject.toml Removes NumExpr and updates the package version. —
plans/​portable-jit-api.md Records the rejected portable-JIT API decision. —
plans/​menudet-jit-expanded-conformance.json Records expanded native conformance results. —
plans/​mathlib-performance.md Documents math backend performance observations. Nit: Replace the obsolete PortableKernel reference with MenudetKernel, or mark it as historical. (2 votes)
plans/​lazyexpr-prefilter-before-profile.txt Records pre-optimization profiling data. —
plans/​lazyexpr-prefilter-after-profile.txt Records post-optimization profiling data. —
plans/​compute-expr-udf-profile-before.txt Preserves pre-optimization UDF profiling data. —
plans/​compute-expr-udf-legacy.md Preserves historical benchmark context. —
examples/​vlstore-lazyudf.py Migrates an object-store example to Menudet. —
examples/​ndarray/​portable_artifact.py Demonstrates standalone artifact execution. —
examples/​ndarray/​mandelbrot-pure-dsl.ipynb Labels the retired DSL notebook as historical. —
examples/​ndarray/​mandelbrot-dsl.ipynb Labels historical DSL comparison content. —
examples/​ndarray/​malformed_dsl.py Demonstrates Menudet validation and execution errors. —
examples/​ndarray/​ironpill1.ipynb Updates benchmark engine documentation. —
examples/​ndarray/​dsl_save.py Demonstrates Menudet recipe persistence. —
examples/​ndarray/​black-scholes_hist-dsl.ipynb Labels historical DSL benchmark content. —
examples/​menudet.py Adds array, table, and reduction examples. —
examples/​menudet_decorator.py Demonstrates Menudet authoring and specialization. —
examples/​embedded-expr-udf-b2z.py Migrates embedded UDF execution to Menudet. —
doc/​reference/​schunk.rst Documents Menudet persistence support. —
doc/​reference/​save_load.rst Documents safe and full deserialization policies. —
doc/​reference/​objectarray.rst Updates ObjectArray persistence documentation. —
doc/​reference/​msgpack_serialization.rst Documents Menudet MessagePack recipes. —
doc/​reference/​misc.rst Documents the finfo alias. —
doc/​reference/​menudet.rst Adds the Menudet API reference. —
doc/​reference/​menudet_accuracy.md Documents numerical behavior and limits. —
doc/​reference/​lazyarray.rst Updates LazyArray and legacy DSL guidance. —
doc/​reference/​index.rst Includes Menudet reference pages. —
doc/​reference/​ctable.rst Documents table concurrency and Menudet columns. —
doc/​reference/​additional_funcs.rst Documents complex and remainder functions. —
doc/​guides/​index.rst Adds Menudet and migration guides. —
doc/​getting_started/​installation.rst Fixes installation-extra formatting. —
doc/​development/​menudet_certification.md Documents numerical certification methodology. —
doc/​development/​index.rst Includes Menudet development documentation. —
doc/​development/​expression_evaluator_migration.md Documents the typed evaluator rollout. —
doc/​conf.py Updates documentation exclusions and autodoc handling. —
CMakeLists.txt Pins and configures the native Menudet dependency and bridges. Moderate: Add dsl_array_bridge.h to the custom command dependencies to prevent stale generated Cython output. (2 votes)
bench/​qualification/​result.schema.json Defines qualification result structure. —
bench/​qualification/​resources.py Captures runner resource fingerprints. —
bench/​qualification/​requirements.txt Pins qualification dependencies. —
bench/​qualification/​profiles/​smoke.json Defines the smoke qualification profile. —
bench/​qualification/​profiles/​observation.json Defines the observation qualification profile. —
bench/​qualification/​profiles/​macos-arm.json Defines the macOS ARM qualification profile. —
bench/​qualification/​measurement.py Implements bounded timing and compiler-cache isolation. —
bench/​qualification/​historical-controls.json Records historical performance controls. —
bench/​qualification/​freshness.py Adds a qualification freshness watchdog. —
bench/​qualification/​__init__.py Identifies the qualification package. —
bench/​optim_tips/​README.md Documents the Menudet Tip 11 qualification. —
bench/​ndarray/​sum-linear-idx.py Migrates coordinate and reduction benchmarking. —
bench/​ndarray/​ramp-comparison.py Compares ramp construction routes. —
bench/​ndarray/​ramp-cold.py Measures cold ramp latency. —
bench/​ndarray/​profile-lazyexpr-prefilter.py Profiles native prefilter dispatch. —
bench/​ndarray/​native-graph-preparation.py Compares native graph preparation paths. Moderate: Make Git revision capture optional or resolve it independently of the current working directory. (1 vote)
bench/​ndarray/​menudet-ramp.py Measures Menudet ramp setup and evaluation. —
bench/​ndarray/​eye-comparison.py Compares eye-generation routes. —
bench/​ndarray/​evaluator_migration.py Compares legacy and typed evaluator routes. —
bench/​ndarray/​compute_expr_udf.py Provides Menudet predicate benchmark kernels. —
bench/​menudet-profile-reader.c Adds a profiling-only compressed reader. —
bench/​menudet-integer-increment.py Benchmarks resident integer increment routes. —
bench/​js-transpiler/​README.md Labels JavaScript DSL benchmarks as historical. —
bench/​chicago-taxi/​string-ops-menudet-prefilter-full.json Records compressed string-operation results. —
AGENTS.md Documents native iteration and testing guidance. —
.github/​workflows/​wasm-tests.yml Expands typed evaluator and WASM qualification. —
.github/​workflows/​perf-qualification-menudet-watchdog.yml Adds scheduled qualification freshness checks. —
.github/​workflows/​cibuildwheels.yml Adds Menudet wheel and numerical qualification. —
.github/​menudet-performance-baseline.json Defines the performance baseline. —
.github/​menudet-native-ref Pins the experimental native revision. —

Review effort: Lite

Comment thread src/blosc2/proxy.py Resolved
Comment thread CMakeLists.txt Resolved
Comment thread plans/mathlib-performance.md Resolved
@FrancescAlted

Copy link
Copy Markdown
Member Author

I think this is good to go. Will probably continue improving that in the future, but for a 0.1 draft, I think this is a good start. @FabianIsensee feel free to assess performance in your boxes; I have been using a Mac Mini with M4 Pro processor, so thing should (and will!) vary.

@FabianIsensee

Copy link
Copy Markdown
Contributor

Hi Francesc,

thanks for merging! Here are numbers from two of our boxes: the original 300×512×512 workload from my Nov 3 comment, and the typed-evaluator micro-benchmarks from #754.

Setup. main right before #741 (126fa8f: numexpr 2.14.2 + miniexpr 3f4db93) against main with #741 (5ff9823, miniexpr 95dbc243). Both use c-blosc2 fa29efc0 and are Release wheels built with conda-forge GCC 14.4 (as the PyPI wheels use GCC 14), NumPy 2.5.4, Python 3.12. Boxes: AMD Ryzen 9 7950X (16 cores, blosc2's default of 28 threads) and NVIDIA Grace (Neoverse V2, BLOSC_NTHREADS=16 pinned to 16 of its 72 cores). Times are in ms: medians over 6 interleaved rounds, each round the median of 5-7 calls (200 for the 100×100 case). Every process ran alone on its machine.

Correctness. With #741, all 30 cases give NumPy's values and dtypes on both boxes. Before #741, int32 (a + b) * abs(a) crashed (double free), int32 a / b returned float32 with wrong values, and int32 a % b returned C fmod signs for negative a (marked † below: those times are for wrong results).

Original workload (300×512×512 float32, default chunks)

Ryzen before Ryzen with #741 ratio Grace before Grace with #741 ratio
(a + b)[:] 107 58.8 0.55x 43.7 26.5 0.61x
a.std() 74.3 38.2 0.51x 60.0 26.2 0.44x
a.mean(), a.std() 89.7 55.7 0.62x 71.5 37.8 0.53x
a.sum(axis=0) 28.1 27.2 0.97x 25.2 30.8 1.22x
where(a > 100, a, 0).compute() 482 462 0.96x 805 813 1.01x
100x100 a.std() 0.60 0.66 1.10x 0.81 0.87 1.07x

The std()/var() slowdown from my Nov 3 comment is gone: 472 ms on the PR head then, 38 ms now on the Ryzen. std() is now about 2x faster than before #741 on both boxes.

Micro-benchmarks from #754 (200×512×512, chunks (10, 512, 512), blocks (1, 128, 512))

Ryzen before Ryzen with #741 ratio Grace before Grace with #741 ratio
f32 (a + b)[:] 58.4 35.8 0.61x 22.3 18.4 0.83x
f32 (a * 2.5 + 1)[:] 50.5 31.7 0.63x 16.8 12.4 0.74x
f32 sin(a)[:] 52.7 42.0 0.80x 25.6 39.5 1.54x
f32 (a > 100).sum() 28.1 14.6 0.52x 16.6 16.3 0.98x
f32 where(a > 100, a, 0)[:] 63.1 35.6 0.56x 21.9 30.4 1.39x
f32 (a + b > 200)[:] 30.3 25.5 0.84x 22.3 24.1 1.08x
mask (a > 100) & (b < 100) [:] 38.4 35.8 0.93x 26.7 31.7 1.19x
mask ((a > 100) & (b < 100)).sum() 42.3 29.8 0.70x 27.2 31.3 1.15x
mask where((a > 90) & (a < 110), a, 0)[:] 97.0 42.5 0.44x 49.0 33.6 0.69x
f32 a.sum() 7.6 8.7 1.15x 9.1 8.7 0.96x
f64 (a * 2 + 1)[:] 90.3 55.7 0.62x 25.8 17.6 0.68x
f64 (a + f32)[:] 98.7 63.0 0.64x 32.6 24.0 0.74x
i32 (a + b)[:] 59.1 31.4 0.53x 26.1 20.7 0.79x
i32 (a * 3 + 1)[:] 55.0 27.6 0.50x 23.3 16.7 0.71x
i32 (a / b)[:] 62.6† 47.1 0.75x 31.7† 26.8 0.84x
i32 (a + 0.5)[:] 78.9 45.7 0.58x 28.3 19.9 0.70x
i32 (a < 2.5).sum() 31.7 19.0 0.60x 24.1 20.0 0.83x
i32 a.sum() 13.5 13.2 0.98x 15.2 13.4 0.88x
i16 + u8 [:] 33.9 18.4 0.54x 14.1 9.0 0.64x
i32 (a % b)[:] 76.4† 130 1.70x 50.9† 157 3.08x
bool (p & q).sum() 9.3 6.9 0.75x 10.6 7.0 0.66x
bool (p | q)[:] 20.0 10.4 0.52x 9.5 7.2 0.76x
bool where(p, a, b)[:] 61.6 171 2.78x 26.5 67.7 2.55x
i32 (a + b) * abs(a)[:] crash 78.6 crash 62.1

The 8-147x slowdowns of the int, bool and mask cases in #754's table (fe932dc) are gone. Of the 23 cases that ran before #741, 20 are as fast or faster on the Ryzen (most 0.5-0.65x), 16 on Grace.

What is still slower, in case it helps:

  1. Three cases are declined by the typed route. They run through fast_eval() chunk by chunk with NumPy (evaluate_chunk() / eval) instead of compute_typed_prefilter() (cProfile on Grace): int32 a % b (1.7x on the Ryzen, 3.1x on Grace), where(p, a, b) with a bool p and float32 a, b (2.8x / 2.6x), and int32 (a + b) * abs(a) (79 / 62 ms; it crashed before, and the legacy evaluator takes 33 / 23 ms but returns wrong values).
  2. sin seems to use the system libm by default on Linux (same time and same error as math_backend="system"). Under blosc2.jit_options(math_backend="sleef"), sin(a)[:] takes 30-32 ms instead of 42-44 ms on the Ryzen, and 18.5 ms instead of 39 ms on Grace (before Introduce Menudet 0.1 draft for portable array and table computation #741: 53 and 26 ms). The max abs error against NumPy goes from 6e-8 to 1.2e-7. On Grace that turns a 1.5x slowdown into a 1.4x speedup. Would SLEEF be the better default on Linux at the default accuracy?
  3. Grace only: where(a > 100, a, 0)[:] 1.39x, the two-comparison masks 1.15-1.19x, a + b > 200 1.08x. where and the masks run in compute_typed_prefilter() (no fallback), so its kernels seem slower on Neoverse V2 than the old miniexpr ones. jit=True makes where slower still on both boxes (55 vs 29 ms on Grace, 54 vs 35 ms on the Ryzen). Also Grace only: a.sum(axis=0) 1.22x (25 -> 31 ms; it goes through reduce_slices(), I did not look further).
  4. Small ones: float32 a.sum() 7.6 -> 8.7 ms on the Ryzen; 100×100 a.std() 0.60 -> 0.66 ms (Ryzen) and 0.81 -> 0.87 ms (Grace).

Legacy evaluator (BLOSC2_EXPRESSION_EVALUATOR=legacy, with #741). It is faster than the typed default in many elementwise cases (Ryzen: f64 a * 2 + 1 40 vs 56 ms, where(a > 100, a, 0) 25 vs 36 ms, mask [:] 27 vs 36 ms), so the typed kernels may have some headroom there. It is 3.4-8.9x slower than before #741 in the comparison reductions ((a > 100).sum(), mask .sum(), (a < 2.5).sum()). It still gets three int32 cases wrong: a / b truncates (701 / 807 -> 0.0), (a + b) * abs(a) returns garbage for nearly every element, and a % b keeps C remainder signs. Fine if it is on its way out. Full tables:

legacy evaluator tables
Ryzen before Ryzen with #741 ratio Grace before Grace with #741 ratio
(a + b)[:] 107 59.2 0.55x 43.7 35.6 0.81x
a.std() 74.3 44.0 0.59x 60.0 27.3 0.45x
a.mean(), a.std() 89.7 60.7 0.68x 71.5 40.0 0.56x
a.sum(axis=0) 28.1 27.6 0.98x 25.2 31.2 1.23x
where(a > 100, a, 0).compute() 482 450 0.93x 805 791 0.98x
100x100 a.std() 0.60 0.53 0.88x 0.81 0.68 0.84x
Ryzen before Ryzen with #741 ratio Grace before Grace with #741 ratio
f32 (a + b)[:] 58.4 30.1 0.51x 22.3 18.0 0.81x
f32 (a * 2.5 + 1)[:] 50.5 25.1 0.50x 16.8 11.9 0.71x
f32 sin(a)[:] 52.7 53.1 1.01x 25.6 53.2 2.08x
f32 (a > 100).sum() 28.1 106 3.78x 16.6 134 8.07x
f32 where(a > 100, a, 0)[:] 63.1 25.1 0.40x 21.9 12.2 0.56x
f32 (a + b > 200)[:] 30.3 22.1 0.73x 22.3 17.4 0.78x
mask (a > 100) & (b < 100) [:] 38.4 27.3 0.71x 26.7 20.4 0.76x
mask ((a > 100) & (b < 100)).sum() 42.3 279 6.60x 27.2 241 8.87x
mask where((a > 90) & (a < 110), a, 0)[:] 97.0 41.7 0.43x 49.0 19.4 0.40x
f32 a.sum() 7.6 8.4 1.11x 9.1 8.8 0.98x
f64 (a * 2 + 1)[:] 90.3 40.3 0.45x 25.8 17.1 0.66x
f64 (a + f32)[:] 98.7 47.1 0.48x 32.6 23.6 0.72x
i32 (a + b)[:] 59.1 30.6 0.52x 26.1 19.9 0.76x
i32 (a * 3 + 1)[:] 55.0 26.5 0.48x 23.3 15.9 0.68x
i32 (a / b)[:] 62.6† 41.6 0.66x 31.7† 29.0 0.92x
i32 (a + 0.5)[:] 78.9 36.2 0.46x 28.3 19.2 0.68x
i32 (a < 2.5).sum() 31.7 107 3.37x 24.1 131 5.44x
i32 a.sum() 13.5 13.6 1.01x 15.2 14.8 0.97x
i16 + u8 [:] 33.9 18.3 0.54x 14.1 9.5 0.67x
i32 (a % b)[:] 76.4† 50.4 0.66x 50.9† 45.1 0.89x
bool (p & q).sum() 9.3 9.0 0.96x 10.6 10.0 0.94x
bool (p | q)[:] 20.0 12.0 0.60x 9.5 7.2 0.76x
bool where(p, a, b)[:] 61.6 32.7 0.53x 26.5 22.3 0.84x
i32 (a + b) * abs(a)[:] crash 32.8 crash 22.9

I can share the scripts if you want to run the same cases on your Mac.

AI disclosure: the measurements and this comment were prepared with Claude Code; I reviewed them.

Best,
Fabian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants