HermitCrab allocation: FeatureStruct clone dominates; jemalloc not applicable

Aperta
#632 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@johnml1135 ci sta già lavorando.

Dal 11/11/2026.

  • #633 di @johnml1135 — aperta

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
38/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
csharp
Ambito
performance

Direzione di ricerca

The profiling points to FeatureStruct.Clone and VariableBindings cloning, but the issue does not name source files or tests. Start by locating FeatureStruct.Clone and the copy constructor, then trace callers that clone VariableBindings in Unify, Subsumes, and IsUnifiable. Compare structural sharing or copy-on-write and avoiding the copies map for structs without reentrancies; done means reduced cloning with measurements on a quiet machine using the same harness and workloads.

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

Descrizione

HermitCrab allocation: FeatureStruct cloning is about 60% of parse bytes; allocator and GC-mode levers are dead ends
Claude-drafted.

About 60% of HermitCrab parse allocation bytes come from FeatureStruct clone machinery, and no allocator or GC-mode change removes that cost. The one host-side lever, DOTNET_GCgen0size=10000000 (hex, 256 MB), cuts gen0 collections about 10x for a noisy wall-time gain of roughly 6 to 14%. The next step is to reduce the cloning itself, not to tune the allocator.

What we know (numbers and method in the details):

  • The clone machinery (the copies Dictionary and its buckets, _definite and VariableBindings clones, SymbolicFeatureValue) is the bulk of allocated bytes on Amharic. GC pause is 450-670 ms of a 2.4-3.1 s pass, 15-25% of wall time.
  • jemalloc via LD_PRELOAD and jemalloc.NET do not apply. Under 1% of allocation events are native, and the jemalloc timing matched a no-op GC control.
  • Server GC, non-concurrent GC, TieredPGO=0 and TieredCompilation=0 gave no win. The last two were 32-35% slower on Amharic.
  • The pooled copies scratch map cuts allocated bytes 10-17%, but wall time is mixed across grammars, so it is not proposed as a PR.

Next: reduce FeatureStruct cloning (structural sharing or copy-on-write, and no copies map for structs without reentrancies) and re-measure on a quiet machine with the same harness. Related open issues: #586 and #587.

Where the bytes go (GCAllocationTick sampling, Amharic 40 words, about 55k ticks)
  • 13.1% Entry[FeatureValue,FeatureValue][] and 12.1% Int32[] (Dictionary buckets): the per-clone copies map in the FeatureStruct copy constructor, plus the _definite maps.
  • 12.4% Entry[string,FeatureValue][]: VariableBindings clones in Unify, Subsumes and IsUnifiable. VariableBindings clones on every call.
  • 12.3% Entry[Feature,FeatureValue][]: _definite of each cloned FeatureStruct.
  • 11.2% SymbolicFeatureValue, 7.0% UlongSymbolicFeatureValueFlags, 3.4% Annotation, 2.8% FSA Register arrays.
  • Net: about 60% of bytes are FeatureStruct clone machinery. FeatureStruct.Clone allocates a Dictionary, buckets and entries per clone even when the struct has no reentrancies.
Method and caveats
  • Harness: a benchmark program in the machine worktree (branch alloc-bench, built against origin/master 0cbf85f1, not pushed). It loads PanGloss samples/data for amharic, aweti and mbugwe (*-hc.xml), builds Morpher(..., maxDegreeOfParallelism 1), and parses the first N words of the matching word list.
  • Per process: 1 warm pass and 2 timed passes; a new process per sample; configs interleaved per round; net10.0 Release.
  • Each cell is n=5 to 15. "Median" is the mean of the two timed passes per process, then the median across processes.
  • Machine load average ran 7.4 to 24.5 on 20 cores. Absolute times are not comparable with a quiet machine, and wall-time spread is 20-40%.
  • Single thread only. The default multi-word parallel case under Server GC was not measured, and the Morpher default parallelism and Server GC interplay was not studied.
  • The Sena word list in PanGloss samples/data parsed zero words in the harness. Not investigated.
Run 2: Amharic 40 words, 8 rounds, interleaved (most controlled)
config median ms min-max vs base GC pause ms gen0/1/2 GCs
DOTNET_GCgen0size=0x10000000 (256 MB) 2668 2361-3152 -13.5% 450 21/2/0
gen0size 256 MB + gcConcurrent=0 2708 2373-3478 -12.2% 569 21/0/0
gen0size 1 GB 2786 2358-3260 -9.7% 470 21/2/0
GCgen0MaxBudget=1 GB (no-op control, no GC change) 2862 2256-3741 -7.2% 650 229/90/9
LD_PRELOAD jemalloc 2864 2125-3354 -7.2% 636 228/80/8
base 3086 2497-4010 0 670 229/89/9
gcConcurrent=0 3268 2326-4469 +5.9% 582 84/19/3

The no-op control (GCgen0MaxBudget, identical GC counts) reads -7.2%, the same as jemalloc, so the jemalloc result is noise. Allocated MB is identical in all configs (2745-2747).

Run 1: Amharic 60 words and Aweti 15 words, 5 rounds (medians vs base)
config Amharic Aweti
jemalloc -6.9% -12.9%
gen0size 256 MB -6.2% -14.1%
gen0size 64 MB +2.7% +26.1%
gcServer=1 +4.9% -4.9%
gcConcurrent=0 +9.2% +8.4%
TieredPGO=0 +32.0% -3.9%
TieredCompilation=0 +35.2% +11.6%

Base spreads: 2790-3838 ms (Amharic) and 706-1052 ms (Aweti). Effects under about 10% are not distinguishable from noise.

Why GC config is not a PR
  • GCgen0size is the only GC knob with a consistent direction: -6 to -14% in two batches, GC pause 670 to 450 ms, gen0 GCs 229 to 21.
  • It has no runtimeconfig name (environment variable only), so a library cannot set it, and a host's GC mode is the host's decision.
  • The gain is within about 2x the noise floor.
  • Proposed docs-only note for hosts: "DOTNET_GCgen0size=10000000 (hex, 256 MB) may cut HermitCrab gen0 collections 10x; measure first."
  • GCgen0MaxBudget raised to 1 GB did nothing (equal to base within noise).
Pooled `copies` scratch map in FeatureStruct.Clone (code lever, not proposed as a PR)

The patch takes a thread-static dictionary out of its slot, clears it after each Clone, and is not a result cache. Bytes allocated per pass are deterministic: Amharic 2747 to 2292 MB (-17%), Aweti 493 to 442 MB (-10%).

Interleaved A/B, same build otherwise:

workload n median ms base to pool paired ratio (pool/base) pool faster
Amharic 40 (run 3) 10 2566 to 2362 (-8.0%) 0.883 8/10
Aweti 15 (run 3) 10 793 to 760 (-4.2%) 1.028 3/10
Amharic 40 (run 4) 15 2671 to 2457 (-8.0%) 0.954 11/15
Mbugwe 12 (run 4) 15 1791 to 1741 (-2.8%) 1.018 6/15

Byte cuts of 10-17% do not become a repeatable wall-time win. The Amharic gain is the only one at the 5% level, and it is smaller in paired ratio than in medians. Aweti and Mbugwe are flat to slightly slower on paired ratio. It is a candidate to revisit on a quiet machine with CPU counters.

jemalloc and jemalloc.NET
  • jemalloc LD_PRELOAD: a full Aweti parse made 28,562 native mallocs (about 10 MB live) against 493 MB of managed allocation in one pass. The GC reserves through mmap, bypassing malloc. Native malloc is well under 1% of allocation events.
  • jemalloc.NET (allisterb): last commit and release v0.2.4 on 2018-01-11, so unmaintained since 2018. It targets netcoreapp2.0 (C# 7.1), has no NuGet package, and ships Windows-oriented native binaries with no Linux binaries. It offers native buffers of unmanaged primitives only. HermitCrab's hot data are reference-type object graphs, so there is no site where a primitive buffer would replace the dominant allocations. Machine ships netstandard2.0 libraries, so the dependency is not acceptable.
Lingua principale
C#
Stelle
29
Fork
16
Merge medio
4g 11h
PR unite (30g)
14

Preparare l'ambiente

  • Nessun Dockerfile né file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

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 sillsdev/machine

Tutte le issue di sillsdev/machine

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.