Symbolica WASM versus native wheels
Measured on 2026-10-29 with matching PyPI 3.0.2 wheels, both reporting
symbolica-e227fe7, on an AMD Ryzen 9 5900X running Linux 7.2.11-arch1-1.
Native CPython is 3.14.8; Pyodide 314.0.7 uses CPython 3.14.2, ABI 2026_0.
Node.js is v26.12.1.
| Operation | Native | WASM | WASM/native |
|---|---|---|---|
Parse (x+y+1)^30 |
1.564 µs | 2.293 µs | 1.47× |
Expand (x+y+1)^30 |
430.5 µs | 670.5 µs | 1.56× |
Factor x^12-y^12 |
268.9 µs | 436.4 µs | 1.62× |
Differentiate (x^2+y^2)^20 |
1.428 µs | 1.997 µs | 1.40× |
Evaluate (x+y)^12 at (2,3) |
0.8006 µs | 4.289 µs | 5.36× |
Parse expanded (1+x+y)^30 (496 terms) |
507.3 µs | 685.4 µs | 1.35× |
Expand (1+x+y+z)^30 (5,456 terms) |
6052 µs | 9373 µs | 1.55× |
Differentiate expanded (1+x+y+z)^30 |
6235 µs | 8788 µs | 1.41× |
Directly evaluate expanded (1+x+y)^30 |
128.6 µs | 1404 µs | 10.91× |
| Dense polynomial multiplication (455 × 364 terms) | 799.9 µs | 1606 µs | 2.01× |
| Exact polynomial division (1,540 ÷ 120 terms) | 775.1 µs | 1251 µs | 1.61× |
Method
Each backend runs in three fresh processes, pinned to logical CPU 6, in order native/WASM, WASM/native, native/WASM. Per-case warmup is at least five seconds; the small expansion uses ten seconds. Nine subsequent batches target 120 ms each. Reported values are medians of the three process medians. Timers include Python loops, API calls, output allocation and destruction. Input construction, validation, imports, installation and startup are outside timing, except that parsing cases deliberately include parsing. No individual samples were removed.
Large differentiation showed a late speed change in one main-run process. Its reported row therefore uses a separate confirmation: three fresh processes per backend, at least twenty seconds and 2,000 operations of warmup per process, then the same nine batches. Its WASM process medians are 8.854, 8.788, 8.658 ms; native medians are 6.235, 6.214, 6.256 ms. The earlier measurements remain in the raw data. Some other main-run cases vary by approximately 10–15% between processes; these are local measurements, not universal slowdown factors. The earlier short-warmup table is superseded.
Workloads and validation
The large parse and direct evaluation cases use expanded (1+x+y)^30
(496 terms); evaluation uses x=0.1, y=0.2 and default precision. The large
expansion and derivative use (1+x+y+z)^30 (5,456 terms), differentiating in
x. Direct evaluation is Expression.evaluate, without a compiled evaluator.
Multiplication uses expanded (1+x+y+z)^12 and (1+2*x-y+3*z)^11.
Division divides the product of expanded (1+x+y+z)^12 and
(1+2*x-y+3*z)^7 by the latter polynomial. These inputs are adapted from
benches/support/cases.rs in Symbolica's FLINT comparison suite, using the
Python polynomial API on each wheel. FLINT itself is not timed here.
Every process checks the expansion term counts, complete parse round trip, factorization reconstruction, a known derivative, numerical evaluation, and exact quotient reconstruction before timing. The complete 496-term parse input has the same SHA-256 in every process. The comparison includes differences in Python runtimes, numeric backends and wheel configuration, as well as WASM execution. It does not isolate any one of these causes.
Wheel build settings
The published 3.0.2 source distribution specifies Rust opt-level=3, ThinLTO,
one codegen unit, and Emscripten linker -O3 for the WASM performance profile.
Binaryen postprocessing is -O0 plus duplicate-function elimination. This run
uses the original published wheel; it does not change those build settings.
Native wheel SHA-256: 25ddf0b492b1f00a856decdac6f957d97d3f368e50ffb03a4df76e9d68e4d04e.
WASM wheel SHA-256: 7563175271b587541c18d58a2775f09302c08f9b105e630944a3ed9fc4d73f77.
Reproduce
Download the matching native and WASM 3.0.2 wheels from PyPI. Install the native wheel into an isolated Python 3.14 environment. Obtain the Pyodide 314.0.7 runtime with ABI 2026_0. Keep the three scripts below together, then run:
export SYMBOLICA_NATIVE_PYTHON=/absolute/path/to/venv/bin/python
export PYODIDE_ROOT=/absolute/path/to/pyodide/dist
export SYMBOLICA_WASM_WHEEL=/absolute/path/to/symbolica-3.0.2-cp314-abi3-pyemscripten_2026_0_wasm32.whl
python run_wasm_vs_native.py --output /absolute/path/to/fresh-results --runs 3 --cpu 6Repeat the differentiation confirmation with the same command and a different fresh output directory, setting:
export SYMBOLICA_BENCH_OPTIONS='{"case_names":["derivative_5456_terms"],"warmup_seconds":20,"minimum_operations":2000}'All samples and process medians · Python workload · Pyodide launcher · Process runner