perf: `s += a[i]` over a `Uint8Array` parameter is 4x slower than over other typed arrays

Chiusa
#13,774 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Una pull request collegata è già stata integrata.

  • #13777 di @delef — integrata

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
18/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
rust, typescript

Direzione di ricerca

The binding proof is in collectors/spec_abi_sites.rs, which only accepts Expr::TypedArrayNew, while lower/expr_new.rs lowers new Uint8Array(x) to Uint8ArrayNew. Start by checking how spec_ta_kind_is_numeric and the TaPtr path handle TYPED_ARRAY_KIND_UINT8, then confirm the sum$spec_b_b entry with the reproduction. Done means the specialized entry becomes sum$spec_ta* with the same output. A linked PR (#13777) is already open, so review that rather than starting a parallel fix.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

What happened

s += a[i] over a Uint8Array parameter runs about 4× slower than the same loop over a Uint16Array or any other numeric typed array. A Uint8Array argument never reaches its callee through the typed-array spec ABI (TaPtr). The callee keeps the boxed ABI, the accumulator s loses its Number-by-construction proof, and every += goes through js_dynamic_string_or_number_add.

What you expected

The same code for Uint8Array as for the other numeric typed-array kinds, close to Node.

Minimal reproduction

function sum(a: Uint8Array, n: number): number {
  let s = 0;
  for (let i = 0; i < n; i += 1) s += a[i];
  return s;
}

const a = new Uint8Array(1 << 20);
for (let i = 0; i < a.length; i++) a[i] = i & 255;

const t0 = Date.now();
let t = 0;
for (let r = 0; r < 400; r++) t += sum(a, a.length);
console.log(Date.now() - t0, t);
perry compile sum.ts -o sum && ./sum
node --experimental-strip-types sum.ts

Median of 3 runs, ms. All variants print the same sum, 53477376000.

array Perry specialized entry Node
Uint8Array 1442 sum$spec_b_b 250
Uint16Array 356 sum$spec_ta3_b 249

A literal length (new Uint8Array(1048576)) does not help: the entry is sum$spec_b_b either way.

The canonical for (let i = 0; i < n; i++) hides the problem, because the indexed_owner loop versioning serves it with its own loop (307 ms). Other loop forms show it: a += 1 step, a non-zero start, a while loop.

Cause

collectors/spec_abi_sites.rs proves a typed-array binding only when its initializer is Expr::TypedArrayNew { kind, arg }:

if let Stmt::Let {
    id,
    init: Some(Expr::TypedArrayNew { kind, arg }),
    ..
} = s

HIR lowers new Uint8Array(x) to Expr::Uint8ArrayNew(x) instead (lower/expr_new.rs: "Uint8Array stays on the Buffer path above"), so a Uint8Array binding is never proven and every call site passing it is judged Boxed.

The exclusion is not deliberate. The TaPtr code already lists the kind: spec_ta_kind_is_numeric includes TYPED_ARRAY_KIND_UINT8, spec_ta_kind_elem_width maps it to BufferElem::U8, and spec_ta_kind_class_name to "Uint8Array". The runtime object is the same too: js_uint8array_alloc and typed_array_alloc(UINT8, n) both call store_alloc with GC_TYPE_BUFFER_UINT8ARRAY (0x40, KIND_BRANDS[UINT8] = 0), the non-movable byte store that TaPtr relies on. sealed_buffers already treats Uint8ArrayNew as a fresh array, so the .buffer exposure check covers it.

Proposed fix

Accept Uint8ArrayNew(arg) in the TaPtr binding collector as kind TYPED_ARRAY_KIND_UINT8, with the same judge_ctor_arg rules for arg, and treat its argument as a copied source like TypedArrayNew's. I will open a PR.

Environment

  • Perry version: 0.5.1663, main at e79c398b5, built from source with --release
  • Host OS: macOS 27.2, Apple M3 Pro
  • Target: native
  • Node: 26.12.1

Anything else

Related: #13740 (the same proof lost for a non-literal length) and #8619 (the TaPtr read rule).

Lingua principale
Rust
Stelle
4.9k
Fork
166
Merge medio
3h 46m
PR unite (30g)
891

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di PerryTS/perry

Tutte le issue di PerryTS/perry

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.