Skip to content

Perf opportunity: packed-float-array throughput (LangArena port lands Aether at 1.12× Go over 50 tasks) #1986

Description

@paul-hammant

Summary

I ported the full LangArena benchmark suite (50 tasks) to Aether — it's up as aether-lang-dev/LangArena#add-aether-benchmarks, all 50 checksums matching on both the small (test.js) and production (run.js) inputs. This issue is the performance read-out, filed as an optimization opportunity rather than a bug: Aether comes out Go-class overall, with one clearly-localized weak spot worth attacking.

Method (and the honest caveats)

  • Aether was measured on a Chromebook-class Intel i7-1265U (2 P-core + 8 E-core, 15 W, single-channel-ish memory — a laptop, thermally throttled).
  • The other 24 languages come from the published LangArena results (results/2026-09-03), measured on the author's AMD Ryzen 7 3800X desktop (8 full cores, ~105 W).
  • Different machines → absolute seconds aren't comparable. So every number below is a ratio to Go, with Go measured on each machine. Go is the common yardstick and the machine delta largely cancels. Lower = faster; 1.00 = same speed as Go.
  • I sanity-checked the machine delta directly (Go vs Go, same code+inputs): this Chromebook is ~1.17× slower than the reference desktop on geomean, but ±2× per benchmark — memory-bound/threaded work up to 2.5× slower here, single-threaded cache-resident bursts sometimes faster. That variance is exactly why the ratio-to-Go framing matters.
  • Single run, no repeat-averaging. Directional, not a leaderboard.

Headline: Aether is Go-class on this suite

Go-anchored geomean across all 50 benchmarks:

Language geomean vs Go
C / Gcc 0.58
Rust 0.63
Java / OpenJDK 0.86
Crystal 0.89
Zig 0.92
C# / JIT 0.93
Go 1.00
Nim 1.02
Aether 1.12
Swift 1.78
Dart / AOT 1.98
Python 3.05
Ruby 10.92

1.12× Go over 50 tasks — right next to Nim, ahead of Swift/Dart and every scripting language. For a young language compiling through C, that's a strong baseline. Aether is actually faster than Go on ~20 of the 50 (binarytrees-arena 0.37, calculator-interpreter 0.36 — beat everything measured, maze family 0.54–0.65, base64-encode 0.50, sha256 0.74, raytracer 0.71, and the parse/regex tasks).

The opportunity: packed-float-array throughput

The geomean is dragged almost entirely by a single, coherent cluster — dense std.floatarr inner loops:

Benchmark Aether / Go Aether (s) Go (s) note
Matmul::Single 3.51× 14.42 4.11 sequential dense matmul
Matmul::T4 11.18× 18.73 1.68 ← see "no parallelism" below
Matmul::T8 13.28× 15.50 1.17
Matmul::T16 14.50× 15.36 1.06
CLBG::Nbody 4.96× 5.53 1.12 float struct-of-arrays, tight loop
Etc::NeuralNet 7.67× 10.32 1.35 dense weight×output sums
CLBG::Spectralnorm 1.58× 1.46 0.93 milder, same shape

Two distinct root causes, both addressable:

1. floatarr element access doesn't lower to a vectorizable load/store.
Every read/write goes through floatarr.floatarr_get_unchecked(a, i) / _set_unchecked(a, i, v) — a call on an untyped ptr handle. The C backend (and thus LLVM/gcc behind it) can't prove aliasing/stride the way it can for a raw double[], so the matmul/nbody/nn inner loops don't auto-vectorize the way C/Zig/Java's do. The milder Spectralnorm (1.58×) is the same loop shape at smaller scale, which is consistent with "constant per-element overhead not amortized," i.e. the accessor call itself.

This is the same root the deferred index-sugar ask names (asks/REPLY-index-sugar-for-intarr-floatarr-longarr.md): the handle is a bare ptr with no element type. A distinct floatarr/intarr/longarr handle type carrying its element kind would let the front end lower a[i] (and the accessors) to a typed, aliasing-friendly load/store — enabling both the ergonomic sugar and the codegen that vectorizes. The perf table is the concrete payoff argument for doing that type-system change.

2. The parallel Matmul variants aren't parallel in Aether.
Look at T4/T8/T16: Go scales 1.68 → 1.17 → 1.06 s as it uses more cores; Aether is flat at ~15–19 s. The port runs those variants sequentially (the checksum only reads one output cell, so I didn't parallelize them). That's a port limitation, not a language one — but it's a natural showcase for Aether's actor scheduler: a worker-pool matmul over spawn + typed messages would be the idiomatic Aether answer, and a much better demo of what the runtime is actually built for than a bare loop. Worth having as a canonical example regardless of the accessor work.

One genuine outlier to look at separately

Distance::NGram = 26.6× Go (24.9 s vs 0.94 s). Not float — this is the map-heavy path. My port keys an std.map by 4-grams rendered with string.from_int, so every n-gram does an int→string alloc + string-hash. Go/Rust/Zig use a native integer-keyed map. An integer-keyed map (or letting std.map take non-string keys) would close most of this. Filing as a note because it's a different subsystem from the float story.

What I'm not claiming

  • Not a machine-fair leaderboard (Chromebook vs desktop; ratio-to-Go is the mitigation, not a substitute for running on identical hardware).
  • The parse/regex tasks that show 0.00× (Json::ParseDom/Mapping, CSV) are partly too fast because my port takes shortcuts on those (documented in the port); don't read those as headline wins.
  • Single run, no warmup-averaging.

Suggested follow-ups (in impact order)

  1. Distinct handle types for intarr/floatarr/longarr carrying element kind → typed, vectorizable element access (fixes the whole Matmul/Nbody/NeuralNet cluster and unlocks a[i] sugar). This is the big one.
  2. Integer-keyed maps (or non-string keys in std.map) → fixes Distance::NGram and any hot integer-keyed lookup.
  3. A canonical actor-parallel matmul example — turns the T4/T8/T16 "weakness" into a runtime showcase.

Happy to re-run on desktop-class hardware for machine-fair numbers, add absolute-time tables, or drill into the generated C for any of these. Raw data and the analysis script are in the LangArena port branch.

(Filed from the LangArena port line; numbers reproducible from aether-lang-dev/LangArena@add-aether-benchmarks + the results/2026-09-03 reference set.)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions