Share
Embed
Transcript
40 slides
-
Optimizing With Static Hermes N E I L D H
A R , M E T A 2 -
What is Static Hermes? • Optional sound type annotations to
improve performance • Allow ahead of time compilation to compact bytecode or fast native binaries • Tight native integration through zero-cost calls to C functions 3 -
Benefits 4 • Improve performance of existing Flow/TypeScript with minimal
changes • Avoid needing to switch from JS to C++ for native interop -
Are we Static Hermes yet? • Fully open source, in
the static_h branch of the facebook/hermes repo • Not ready for general use • Many typed features implemented (e.g. unions, classes, generics) • Major improvements to untyped code spec compliance and performance in the works 5 -
Sound Types • Type annotations in Flow and TypeScript are
unsound, and do not guarantee that types are correct at runtime • Static Hermes modifies some JS semantics to allow efficient sound typing • New soundly typed code can co-exist and interop with unmodified code 8 -
Unsound Types Example function addXY(x: number, y: number): number {
return x + y; } let a: number[] = [1, 2, 3]; addXY(a[10], a[11]); 9 -
Unsound Types Example function addXY(x: number, y: number): number {
return x + y; } let a: number[] = [1, 2, 3]; // Adding undefined + undefined! addXY(a[10], a[11]); 1 0 -
Sound Type Enforcement function addXY(x: number, y: number): number {
return x + y; } let a: number[] = [1, 2, 3]; // RangeError thrown at runtime! addXY(a[10], a[11]); 1 1 -
Class Fields 1 2 class Vector { constructor(x, y, z)
{ this.x = x; this.y = y; this.z = z; } } class Vector { x: number; y: number; z: number; constructor(x: number, y: number, z: number) { this.x = x; this.y = y; this.z = z; } } Untyped Typed print(myVec.z); -
Reading myVec.z 1. Check that myVec is an object 2.
Get the cache entry for the read 3. Compare the object with the cache 4. On match, load from cached offset 5. On fail, full dictionary lookup 1. Load from offset 2 1 3 Untyped Typed -
Array Access 1 4 function getIdx( myArr: number[], i: number
): number { return myArr[i]; } Untyped Typed function getIdx( myArr, i ) { return myArr[i]; } -
Reading myArr[i] 1. Check that myArr is an object 2.
Check if i is index-like 3. If it is, attempt to do an indexed load 4. Check if the value is available 5. If any checks fail, full dictionary lookup 1. Check that i is an integer within bounds 2. Load from the array at i 1 5 Untyped Typed -
myVec.z + myArr[i] 1. Slow property load from myVec 2.
Slow array load from myArr 3. Check if both are numbers 4. If they are, add them 5. If not, fall through to general case (strings, objects, BigInt, etc) 1. Fast load from myVec 2. Fast load from myArr 3. Add the numbers 1 6 Untyped Typed -
Inlining 1 9 1 9 “He will win who knows
when to inline and when not to inline.” soft e -
Motivation • Code tends to have a lot of small
functions • Calls have significant overhead • Most optimizations cannot operate across calls 2 0 -
Inlining Example 2 1 function normal(pos: Vector): Vector { return
Vector_norm(Vector_minus(pos, this.center)); } function Vector_minus(v1: Vector, v2: Vector): Vector { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } function Vector_norm(v: Vector): Vector { var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return new Vector(div * v.x, div * v.y, div * v.z); } -
Inlining Example 2 2 function normal(pos: Vector): Vector { var
v = new Vector( pos.x – this.center.x, pos.y – this.center.y, pos.z – this.center.z ); var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return new Vector(div * v.x, div * v.y, div * v.z); } *Artist’s impression Vector_minus Vector_norm -
Inlining Example 2 3 function normal(pos: Vector): Vector { var
v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; } *Artist’s impression -
Call Analysis function foo(){ let op = (a, b) =>
a + b; function bar(a, b){ return op(a, b); } return bar; } 2 4 -
Call Analysis function foo(){ let op = Math.random() > 0.5
? (a, b) => a + b : (a, b) => a / b; function bar(a, b){ return op(a, b); } return bar; } 2 5 -
Inlining Methods 2 6 class Base { getID(): number {
return 0; } }; function getIDPlusOne(base: Base): number { return base.getID() + 1; } class Derived extends Base { getID(): number { return 42; } }; -
Inlining Methods 2 7 class Base { getID(): number {
return 0; } }; function getIDPlusOne(base: Base): number { return base.getID() + 1; } class Derived extends Base { getID(): number { return 42; } }; -
Inlining Methods 2 8 class Base { getID(): number {
return 0; } }; function getIDPlusOne(derived: Derived): number { return derived.getID() + 1; } class Derived extends Base { getID(): number { return 42; } }; -
Inlining Methods 2 9 class Base { getID(): number {
return 0; } }; function getIDPlusOne(derived: Derived): number { return 43; } class Derived extends Base { getID(): number { return 42; } }; -
Types Enable Inlining 3 0 class Base { getID() {
return 0; } }; function getIDPlusOne(derived) { return derived.getID() + 1; } class Derived extends Base { getID() { return 42; } }; -
Inlining Recap • Eliminate calls by copying a function’s contents
to where it is called • Depends on the compiler’s ability to statically identify what function is being called, types make this much easier • Enables many other optimizations 3 1 -
3 2 1300 ms 120 ms Types 350 ms Inlining
210 ms ??? -
Motivation • JavaScript tends to create many small objects •
Object allocations are expensive • Analysing the values of object fields is difficult 3 4 -
Object Elision Example 3 5 function normal(pos: Vector): Vector {
var v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; } -
Object Elision Example 3 6 function normal(pos: Vector): Vector {
var v = { x: pos.x – this.center.x, y: pos.y – this.center.y, z: pos.z – this.center.z }; var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * v.x, y: div * v.y, z: div * v.z}; } -
Object Elision Example 3 7 function normal(pos: Vector): Vector {
var vx = pos.x – this.center.x; var vy = pos.y – this.center.y; var vz = pos.z – this.center.z; var mag = Math.sqrt(vx * vx + vy * vy + vz * vz); var div = mag === 0 ? Infinity : 1.0 / mag; return {x: div * vx, y: div * vy, z: div * vz}; } -
Elision Inlining class Foo { bar: () => number; constructor(bar:
() => number) { this.bar = bar; } }; 3 8 function myFun(){ let fbar = () => 42; return fbar(); } function myFun(){ let f = new Foo(() => 42); let fbar = f.bar; return fbar(); } function myFun(){ return 42; } function myFun(){ let f = { bar: () => 42 }; let fbar = f.bar; return fbar(); } Inlining Object elision Inlining -
3 9 1300 ms 120 ms Types 350 ms Inlining
210 ms Object elision -
Recap • Static Hermes enforces soundness of declared types •
We use type information to speed up basic operations • Types unlock additional ahead-of-time optimizations like inlining and object elision • These dramatically improve the performance of typed code 4 0 -
Thank You! • For questions or suggestions, reach out to
us on GitHub! o https://github.com/facebook/hermes/discussions/categories/static-hermes • For an introduction to Static Hermes, watch Tzvetan's talk at RN EU 2023 o https://youtu.be/q-xKYA0EO-c • For the raytracer benchmark, check out the static_h branch on GitHub o https://github.com/facebook/hermes/tree/static_h/benchmarks/raytracer 4 1