perf: `s += a[i]` over a `Uint8Array` parameter is 4x slower than over other typed arrays
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
- Ambito
- compilers, performance
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,
mainate79c398b5, 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
- Include un Dockerfile o un file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di PerryTS/perry
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
-
package-audit performance
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
PerryTS/perry#13976 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug package-audit performance
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
PerryTS/perry#13974 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug package-audit
Difficoltà 4/5 3-5 giorni Idoneità per principianti 46/100
I maintainer di solito rispondono entro 1 giorno
-
bug package-audit
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di PerryTS/perry
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
RustCrypto/utils#1597 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
Non-`main` default branchesAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
max-sixty/worktrunk#4727 · 7 commenti · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
I maintainer di solito rispondono entro 1 giorno
-
follow-up
Difficoltà 2/5 1-3 ore Idoneità per principianti 73/100
carrick-sh/carrick#1859 ·
I maintainer di solito rispondono entro 1 giorno