microsoft / microsoft/TypeScript

Performance: Monomorphic AST Nodes

Offen
#59,198 1 Kommentar 18 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Domain: Performance In Discussion Suggestion
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

The problem

JavaScript engines commonly use optimizing compilers that rely on objects having stable "shapes". Meaning access sites for object properties should hopefully only observe objects that all have the same properties, and each of those properties has a consistent internal type, e.g. an integer/string/boolean.

  • Monomorphic means that only a single shape is observed at the site of a dynamic operation.
  • Polymorphic means that 2-4 shapes are observed
  • Megamorphic means that > 4 shapes are observed

Currently accessing common properties of Node such as kind is megamorphic. This means that constructs such as switch (node.kind) and type node type guards (is* functions which test the kind) are much slower than they need to be and this slows down the entire compiler.

The same issue is also present in Type

Proposed solution

Make node monomorphic by having a single object shape with common properties (such as kind, pos, end) and move all other properties in a separate data object in the node.

To preserve compatibility with existing code, we can add accessors for known properties of all nodes:

export class NodeImpl {
    pos: number;
    end: number;
    kind: number;
    id: number;    
    data: any;

    …
    get resolvedSymbol() {
    }
    set resolvedSymbol(value: any) {
        this.data.resolvedSymbol = value;
    }

    get modifiers() {
        return this.data?.modifiers;
    }
    set modifiers(value: any) {
        this.data.modifiers = value;
    }

    //// All other properties of all nodes

}

The same approach can also be used for Type.

Results

The proposed solution was implemented in #58928 for both Node, Type and signature. The performance results from the PR on Node are very promising:

  • a total time win of 16.0% (ranging 10.5% to 23.7%)
  • a checker time win of 20.8% (ranging 16.6% to 26.8%)
  • a memory cost of 2.7% (ranging 1.9% to 4.4%)

This is a memory for performance exchange rate of 5.8 on total time, and 7.6 on checker time.

A breakdown of the contribution of making each type individually monomorphic is also available:

AST Nodes #59190 :
- total time win: 3.3% - 9%
- Check Time win: 3.7% - 11%
- Mem cost: ~0-1%

Types #59191:
- total time win: 4.1% - 20%
- Check Time win: 5.3% - 22.5%
- Mem cost: 0.75%-1.2%

Signatures #59192:
- total time win: 0.45% - 1.72%
- Check Time win: 0.56% - 2.04%
- Mem cost: ~0%

Acknowledgements

This work is the result of a collaboration between me and Ashley Claymore (@acutmore) . Ashley first noticed that all is* guard functions were megamorphic and switching to a switch statement can already improve performance considerably. We both hypothesized that speeding up access to kind in some way would speed up the compiler significantly.

This work builds on the work of Ron Buckton (@rbuckton). Both his work in Node-less megamorphic (without this work the current proposal would not be feasible) and also his creation of Deopt Explorer which was used to drive the experimentation.

Potential issues with the approach

For API clients that dynamically inspect what the properties of a Node are, this will be a breaking change since the properties of a node are no longer directly visible on the Node instance

This new approach allocates more memory, resulting in an increase in memory usage of between 1.9-4.4%. While this cost is not insignificant, the performance wins outweigh the cost.

This new approach is optimized for V8 - other runtimes might see different results both in terms of performance gain and memory usage.

Potential future improvements

Since some property names are common between multiple node kinds, the accessors for them will be megamorphic. While even with this extra cost we still get a significant amount of performance improvement, switching to using data directly might yield even better results.

Alternatively maybe v8 could improve access to base class properties, and this change might no longer be needed in a future version of v8. Ron has raised this issue with v8.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit der Prüfung von PR #58928, den das Issue als Implementierung der vorgeschlagenen monomorphen Änderungen an Node, Type und Signaturen bezeichnet. Prüfe, ob noch Arbeiten an AST Node ausstehen, und vergleiche die Ergebnisse mit den gemeldeten Verbesserungen bei der Compiler-Performance und den Speicherkosten; die Arbeit ist abgeschlossen, wenn die relevanten Shapes monomorph sind, ohne die angegebenen Abwägungen zu überschreiten.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, typescript
Bereich
compilers, performance
Issue-Typ
Refactoring
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
15/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.