Do you know how really your code is working behind? It’s very useful to be aware of mechanisms and optimizations which your engine will perform. I will tell you how your code is processed and run, and how it could affect you in day-by-day development.
Performed at Code Europe 2017: https://www.codeeurope.pl
8. How full-codegen works?
• Compiles JS code directly to native code before executing it
• Needs to compile code as quick as possible
• It’s not optimizing code
• One function at a time, compiling just in time
• Uses Inline Caches to implement loads, stores calls, binary, unary and comparison
• Implementation of IC is stub, generated on the fly, can be cached for common cases
• Stub on beginning has no instructions, each missed call makes it more complicated
Full-codegen
10. How Crankshaft works?
• Only selected "hot" functions are passed for optimizations.
• Hot: marked for optimization -> LazyRecompile -> optimizing
• Hot: uninitialized -> premonomorphic -> monomorphic
• Most of time will mark to optimize at second call
• Sometimes compiler will optimize it immediately (big loops) - on-stack replacement
• Builds Hydrogen control flow graph (SSA)
• Optimizes in Hydrogen graph (only part which can be in parallel to JS)
• Generates Lithium graph
• Emit native instructions
Crankshaft
11. Static single assignment form
Crankshaft
From: https://en.wikipedia.org/wiki/Static_single_assignment_form
x ← 5
x ← x - 3
x<3?
y ← x * 2
w ←y
y ← x - 3
w ← x - y
z ←x + y
x1 ← 5
x2 ← x - 3
x2<3?
y1 ← x2 * 2
w1 ← y1
y2 ← x2 - 3
w2 ← x2 - y?
z1 ←x2 + y?
12. Static single assignment form
Crankshaft
From: https://en.wikipedia.org/wiki/Static_single_assignment_form
x ← 5
x ← x - 3
x<3?
y ← x * 2
w ←y
y ← x - 3
w ← x - y
z ←x + y
x1 ← 5
x2 ← x - 3
x2<3?
y1 ← x2 * 2
w1 ← y1
y2 ← x2 - 3
y3 = Φ(y1,y2)
w2 ← x2 - y3
z1 ←x2 + y3
13. What is Crankshaft optimizing?
• Inline functions - when it’s safe and they are small
• Tag values - e.g. integer, double
• Analyze Uint32 - by default uses 31-bit for small signed integers, allow use of bigger
• Canonicalization - simplify
• Global Value Numbering (GVN) - eliminate redundancy
• Redundant bounds check elimination - remove unneeded checks for arrays
• Dead code elimination
Crankshaft
15. How TurboFan works?
• Uses „sea of nodes” concept
• Can work directly on byte code (works perfectly with Ignition)
• Machine code is emitted
• Able to easily handle more features (e.g. from ES6)
• More aggressive than Crankshaft
• Optimizes progressively
• Has scheduling algorithm
TurboFan
17. How to enable Ignition?
• In Chrome it’s put on A/B tests or you can use flag disable-v8-ignition-turbo
• In Node.js 7+ use --ignition flag
Ignition
18. How Ignition works?
• It’s interpreter
• Compiles to byte code
• Reduce memory usage for compiling (important for mobile)
• Improve startup time
• Simplify compilation pipeline
• Perform some optimizations, e.g. remove dead-code
Ignition
20. How tests were prepared?
• MacBook Pro (early 2015), i5 2.7GHz, 8GB DDR3
• Node.js 7.7.1 (V8 5.5.372.41)
• Time calculated by time node script.js
• Number of iterations vary in test cases
Optimizations & memory representation by examples
21. Try/catch
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
return 1 + 2 * 3 / 4
}
// Set up function which calls function
function test () {
for (var i = 0; i < iterations; i++) {
f(i)
}
}
try {
test()
} catch (e) {
// Do nothing
}
real: 0.663s user: 0.555s sys: 0.027s
Optimizations & memory representation by examples
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
return 1 + 2 * 3 / 4
}
// Set up function which calls function
function test () {
try {
for (var i = 0; i < iterations; i++) {
f(i)
}
} catch (e) {
// Do nothing
}
}
test()
real: 8.826s user: 8.496s sys: 0.071s
22. Try/catch
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
return 1 + 2 * 3 / 4
}
// Set up function which calls function
function test () {
for (var i = 0; i < iterations; i++) {
f(i)
}
}
try {
test()
} catch (e) {
// Do something
}
real: 0.663s user: 0.555s sys: 0.027s
Optimizations & memory representation by examples
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
return 1 + 2 * 3 / 4
}
// Set up function which calls function
function test () {
try {
for (var i = 0; i < iterations; i++) {
f(i)
}
} catch (e) {
// Do something
}
}
test()
real: 8.826s user: 8.496s sys: 0.071s
23. For..in
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
function f () {
var obj = {}
var key
for (key in obj) {
// Do nothing
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1, 2, 3, 4, 5)
}
real: 1.416s user: 1.281s sys: 0.042s
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
var key
function f () {
var obj = {}
for (key in obj) {
// Do nothing
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1, 2, 3, 4, 5)
}
real: 3.181s user: 2.868s sys: 0.055s
Optimizations & memory representation by examples
24. For..in
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
function f () {
var obj = {}
var key
for (key in obj) {
// Do nothing
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1, 2, 3, 4, 5)
}
real: 1.416s user: 1.281s sys: 0.042s
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
var key
function f () {
var obj = {}
for (key in obj) {
// Do nothing
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1, 2, 3, 4, 5)
}
real: 3.181s user: 2.868s sys: 0.055s
Optimizations & memory representation by examples
25. for..of vs classic for loop
// Set up number of iterations
const iterations = 1e8
// Set up data
const arr = [ 0, 1, 2, 3, 4 ]
// Set up function for tests
function f () {
for (var element of arr) {
// Do nothing
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f()
}
real: 13.879s user: 13.662s sys: 0.099s
// Set up number of iterations
const iterations = 1e8
// Set up data
const arr = [ 0, 1, 2, 3, 4 ]
// Set up function for tests
function f () {
for (var i = 0; i < arr.length; i++) {
var el = arr[i]
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f()
}
real: 0.722s user: 0.620s sys: 0.028s
Optimizations & memory representation by examples
26. for..of implementation
class Iterable {
constructor (...elements) {
this.els = elements || []
}
add (...elements) {
this.els.push(...elements)
}
*[Symbol.iterator] () {
yield* this.els
}
}
const a = new Iterable(1, 2, 3, 4)
for (let element of a) {
console.log(element)
}
class Iterable2 {
constructor (...elements) {
this.els = elements || []
}
add (...elements) {
this.els.push(...elements)
}
*[Symbol.iterator] () {
for (let i = 0; i < this.els.length; i++) {
yield this.els[i]
}
}
}
Optimizations & memory representation by examples
27. Mono & polymorphic
operations
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (x) {
return x + 'a'
}
// Set up testing values
const values = [ 0, 1, 2, 3, 4, 5 ]
const length = values.length
// Benchmark function
for (var i = 0; i < iterations; i++) {
var value = values[i % length]
f(value)
}
real: 7.325s user: 6.697s sys: 0.071s
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (x) {
return x + 'a'
}
// Warm function with different types of values
f(undefined), f(null), f('a'), f(true)
// Set up testing values
const values = [ 0, 1, 2, 3, 4, 5 ]
const length = values.length
// Benchmark function
for (var i = 0; i < iterations; i++) {
var value = values[i % length]
f(value)
}
real: 21.012s user: 20.271s sys: 0.159s
Optimizations & memory representation by examples
28. Mono & polymorphic
operations
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (x) {
return x + 'a'
}
// Set up testing values
const values = [ 0, 1, 2, 3, 4, 5 ]
const length = values.length
// Benchmark function
for (var i = 0; i < iterations; i++) {
var value = values[i % length]
f(value)
}
real: 7.325s user: 6.697s sys: 0.071s
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (x) {
return x + 'a'
}
// Warm function with different types of values
f(undefined), f(null), f('a'), f(true)
// Set up testing values
const values = [ 0, 1, 2, 3, 4, 5 ]
const length = values.length
// Benchmark function
for (var i = 0; i < iterations; i++) {
var value = values[i % length]
f(value)
}
real: 21.012s user: 20.271s sys: 0.159s
Optimizations & memory representation by examples
29. Mono & polymorphic
operations• Different types causes different native operations
• Monomorphic stub has only one case
• Megamorphic stub has more cases
• For different cases needs to cover „safety gates” to handle different types
Optimizations & memory representation by examples
30. Objects interpretation
// Set up number of iterations
const iterations = 1e9
const obj = { 0: 1 }
// Set up function for tests
function f (i) {
obj[0] = i
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(i)
}
real: 0.837s user: 0.729s sys: 0.030s
// Set up number of iterations
const iterations = 1e9
const obj = { x: 1 }
// Set up function for tests
function f (i) {
obj.x = i
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(i)
}
real: 0.579s user: 0.554s sys: 0.013s
Optimizations & memory representation by examples
31. Object representation in
memory• Hash tables (dictionaries) - used for difficult objects, slow
• Fast elements - object with integer indexes, handled differently (can be array), small
• Fast, in-object properties - create hidden classes, shared same structure (transitions)
• Methods & Prototypes - Functions go as constant_function to map
• Object can move from one representation to another
Optimizations & memory representation by examples
32. Hidden classes
Optimizations & memory representation by examples
function Point (x, y) {
// Map M0
// ”x”: Transition to M1 at offset 12
this.x = x
// Map M1
// ”x” at 12
// ”y”: Transition to M2 at offset 16
this.y = y
// Map M2
// ”x” at 12, ”y” at 16
// ”do”: Transition to M3 <doSomething>
this.do = doSomething
// Map M3
// ”x” at 12, ”y” at 16
// ”do”: Constant_Function <doSomething>
}
function doSomething (p) { /* ……… */ }
From: https://github.com/thlorenz/v8-perf
function Point (x, y) {
this.x = x
this.y = y
if (x > 10) {
this.big = true
}
}
// Map X
const a = new Point(5, 0)
// Map Y
const b = new Point(20, 0)
33. Hidden classes
Optimizations & memory representation by examples
function Point (x, y) {
// Map M0
// ”x”: Transition to M1 at offset 12
this.x = x
// Map M1
// ”x” at 12
// ”y”: Transition to M2 at offset 16
this.y = y
// Map M2
// ”x” at 12, ”y” at 16
// ”do”: Transition to M3 <doSomething>
this.do = doSomething
// Map M3
// ”x” at 12, ”y” at 16
// ”do”: Constant_Function <doSomething>
}
function doSomething (p) { /* ……… */ }
From: https://github.com/thlorenz/v8-perf
function Point (x, y) {
// Map M0
this.x = x
// Map M1
this.y = y
// Map M2
if (x > 10) {
this.big = true
// Map M3
}
}
// Map M2
const a = new Point(5, 0)
// Map M3
const b = new Point(20, 0)
35. Determine type of variable
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
function f () {
var attr = arguments
if (1 === 1) {
attr = [ 0 ]
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(i)
}
// Set up number of iterations
const iterations = 1e8
// Set up function for tests
function f () {
var attr = arguments
var x = [ 1 ]
if (1 === 1) {
x = [ 0 ]
}
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(i)
}
real: 3.696s user: 3.466s sys: 0.064s real: 1.679s user: 1.454s sys: 0.037s
Optimizations & memory representation by examples
36. Mutate arguments
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
// Modify arguments element
arguments[0] = 0
return arguments[0]
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 31.372s user: 31.064s sys: 0.106s
// Set up number of iterations
const iterations = 1e9
// Set up array-like object to modify property
var dummy = { 0: 1, length: 1 }
// Set up function for tests
function f () {
// Modify dummy element
dummy[0] = 0
return arguments[0]
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 1.854s user: 1.753s sys: 0.029s
Optimizations & memory representation by examples
37. Mutate arguments
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f () {
// Modify arguments element
arguments[0] = 0
return arguments[0]
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 31.372s user: 31.064s sys: 0.106s
// Set up number of iterations
const iterations = 1e9
// Set up array-like object to modify property
var dummy = { 0: 1, length: 1 }
// Set up function for tests
function f () {
// Modify dummy element
dummy[0] = 0
return arguments[0]
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 1.854s user: 1.753s sys: 0.029s
Optimizations & memory representation by examples
38. Mutate arguments variables
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (arg) {
// Modify argument
arg = 0
return arguments[0]
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 31.570s user: 31.233s sys: 0.128s
// Set up number of iterations
const iterations = 1e9
// Set up function for tests
function f (arg) {
// Modify argument
arg = 0
return arg
}
// Benchmark function
for (var i = 0; i < iterations; i++) {
f(1)
}
real: 0.699s user: 0.554s sys: 0.036s
Optimizations & memory representation by examples
39. Methods to find bottlenecks
Optimizations & memory representation by examples
• Find problems by profiler / timeline (e.g. Chrome DevTools)
• Since 6.3: node —inspect-brk: Inspect code with DevTools
• node —trace-opt: trace optimizations on functions
• node —trace-deopt: trace deoptimizations
• node —allow-natives-syntax: Native Syntax for advanced scripting with V8 access
• node —prof: Profile code, generates v8.log file
• node —prof-process v8.log
40. Profiling Node (isolate-xxx-v8.log)
Optimizations & memory representation by examples
[Shared libraries]:
ticks total nonlib name
9 0.2% 0.0% C:WINDOWSsystem32ntdll.dll
2 0.0% 0.0% C:WINDOWSsystem32kernel32.dll
[JavaScript]:
ticks total nonlib name
741 17.7% 17.7% LazyCompile: am3 crypto.js:108
113 2.7% 2.7% LazyCompile: Scheduler.schedule richards.js:188
103 2.5% 2.5% LazyCompile: rewrite_nboyer earley-boyer.js:3604
103 2.5% 2.5% LazyCompile: TaskControlBlock.run richards.js:324
96 2.3% 2.3% Builtin: JSConstructCall
...
[C++]:
ticks total nonlib name
94 2.2% 2.2% v8::internal::ScavengeVisitor::VisitPointers
33 0.8% 0.8% v8::internal::SweepSpace
32 0.8% 0.8% v8::internal::Heap::MigrateObject
30 0.7% 0.7% v8::internal::Heap::AllocateArgumentsObject
...
[GC]:
ticks total nonlib name
458 10.9%
42. What is Garbage Collector for?
• Delete data from memory when it’s no longer needed
• Garbage collector most of time may be slower than managing memory by self
• Program has to stop while garbage collector is working
• Make easier for developer to manage memory
Garbage collector & memory leaks
43. How does GC work?
• There is created tree of nodes (with dependencies)
• Everything what is accessible from root (all global objects, all in branches) is live
• New data is most of time temporary, so probably should be earlier destroyed
Garbage collector & memory leaks
44. Memory zones & collecting
garbage• Young generation = New space, Old generation = Old space
• After surviving for a while in new space, elements are moved to old space
• Allocation to old space is fast, but collecting there is slower
• On new space - scavenge collection, old - mark-sweep (or mark-compact)
• Marking can be processed in chunks
• There are 3 marking states:
• White - object not discovered by GC
• Grey - discovered, but not all neighbors processed
• Black - all of neighbors has been fully processed
Garbage collector & memory leaks
52. Memory zones
• New space - garbage collected quickly, 1-8MB space
• Old pointer space - objects which have pointers to other, moving from „new” after while
• Old data space - objects which contain raw data (objects, strings, arrays etc)
• Large object space - objects larger than size limit for other spaces, never moved
• Code space - JITed instructions, code objects
• Cell space, map space, property cell space - Cells, PropertyCells and Maps
Garbage collector & memory leaks
53. Memory leaks: examples
Garbage collector & memory leaks
function f () {
obj = {
a: 1,
b: 1
}
}
• It might be by accident
• It assigns to this element
• this may be global or window
• Assigned to root, so will not be removed
54. Memory leaks: examples
Garbage collector & memory leaks
// Get some data
var r = getData()
// Process data
function process () {
if (shouldProcess()) {
console.log(r.items)
}
}
// Do something in interval
setInterval(process, 10000)
• Intervals which uses some data
• When they are not needed - clear them
55. Memory leaks: examples
Garbage collector & memory leaks
var theThing = null
function replaceThing () {
var originalThing = theThing
var unused = function () {
if (originalThing) {
console.log("hi")
}
}
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage)
}
}
}
setInterval(replaceThing, 1000)
• Closures can be bad
• unused keeps pointer to originalThing
• someMethod share closure with unused
• Keeps reference to old theThing
56. Memory leaks: how to detect
Garbage collector & memory leaks
• Generate heap dumps (record in Chrome, or e.g. use `heapdump` module)
• Chrome DevTools - JS Profiler: Comparison
• Compare two/three snapshots from different time - you will see what is left
From: http://bit.ly/2pcDTe9
57. Memory leaks: how to detect
Garbage collector & memory leaks
• Generate heap dumps (record in Chrome, or e.g. use `heapdump` module)
• Chrome DevTools - JS Profiler: Comparison
• Compare two/three snapshots from different time - you will see what is left
• Other way: use GCore
gcore `pgrep node`
> ::findjsobjects
object_id::jsprint
object_id::findjsobjects -r
Not contributor, digging deeper is essential for programmers. When junior looks at code, he may not see something, but mid/senior dig little deeper. Also you can encounter analogies between it and other tools/things.
Tell you about V8 itself, its components: /list them/, how does it work and how it affects your code.
We’ve got 45 minutes, I’ll just try to show most important stuff and encourage you to read little more about that. Presentation is available on the web, so you can even look at it later.
V8 is JavaScript engine which executes your code. It’s open-source & mostly maintained by Google. It’s used widely, e.g. Chrome, Node.js, Opera or Vivaldi.
V8 started at 2008, being fastest JS engine. Meanwhile all other engines was trying to beat it.
In 2009 Node.js based on V8 was introduced. It shown that JS can be used also server-side.
In 2010 was introduced 1st optimizing compiler, but have some design issues. Web has become more complicated, so to fix problems with Crankshaft and optimize code in case of asm.js in 2014 V8 team shown TurboFan, 2nd optimizing compiler. It is much more flexible, has been enabled in Chrome in 2015. In 2016 there was a need to decrease memory for compiling, so Ignition was a resolution for that.
2008 - 2 September
2009 - ?
2010 - 7 December
2014 - ?
2015 - ?
2016 - July
2017 - January/February
2017 goal - get rid of Crankshaft and Full-codegen
Looks a little complicated. Three almost independent parts now. Only AST can be reused between. 3 JITs and one interpreter.
In 2017 pipeline will be simplified. TurboFan can use bytecode directly, Source -> Parser -> AST -> Ignition -> Bytecode -> Turbofan -> Optimized code
It’s simply translating JS to native code. It has to be fast, to deliver execution asap. Because of that, it’s not optimizing. JIT compiler, so compiles function just when it’s needed. Operations can be inlined with inline caches. Implementation of IC is stub. It caches operations needed to perform for specific cases. Later I will show you example how it affects you.
It’s JIT. Optimizes hot functions. uninitialized > marked for optimization > optimized (with monomorphic stub) > polymorphic. Most of time mark 2nd call. On-stack replacement - immediately. Uses 2 representations: Hydrogen & Lithium. Most optimizations in hydrogen. hydrogen -> lithium -> native instructions. SSA - Static Single Assignment form.
Hot: earlier it was checking each 1 or 5ms and if seen function several times marked as hot
Always assigning to new variable. Determine by Φ (Phi) which variable should be taken according to branches where algorithm gets in.
Always assigning to new variable. Determine by Φ (Phi) which variable should be taken according to branches where algorithm gets in.
Optimizations:- dynamic type feedback: get information from full-compiler about inline caches (optimizes e.g. for going to key => value)
inlining: when it’s safe, function will be inlined (not large functions, not dangerous
Representation inference: tag values (most important for numbers: raw integer - fastest, double)
static type inference: match type of function (not possible most time, because only one function)
Uint32 analysis: JS uses only 31-bit for small signed integers, marks some types as unsigned to allow bigger
canonicalization: just simplify operations
Global value numbering (GVN): eliminate redundancy - hashes for opcodes and remove them
Loop invariant code motion (LICM): if function in loop doesn’t use loop context, put it to pre-header
Redundant bounds check elimination: remove unneeded checks for arrays
Array index dehoisting: revert LICM for some operations (n++, n += 20)
Dead code elimination
„Sea of nodes” - easier to optimize. not ordering nodes except participating in control flow. change any part of execution. Can work on byte code (Ignition), more aggressive. Uses optimization layers. Layers allow easier handling new features. Can work in parallel increasing optimization.
Ignition is right now in tests phase. As before - in 2017 it will be used widely deprecating Crankshaft and Full-codegen. In Chrome use flag in chrome://flags, in Node use —ignition.
It’s byte code interpreter. Reduces memory usage, simplifies pipeline working with TurboFan. Executes byte code very properly on real-world websites. Additionally startup should be faster. Also has mechanisms for optimizations, e.g. remove dead code. Right now it’s not working fully as expected, is little slower.
Number vary because sometimes I just didn’t want to wait hour for tests.
Many people see try/catch as slow. Compiler when see try/catch can’t optimize - try/catch is too hard because of various cases.
But looking at another code we get more almost 15 times faster. When we put it out of processing function, then processing can be optimized.
Next, less obvious example - we’ve got simple for in loop on some object. We want to try to optimize it - 100kk times we are declaring key, put it outside!
No, it’s slower. I haven’t found a reason for that, but when key variable is out of closure, loop is not optimized. Probably because loop invariant code motion (LICM). It’s not obvious.
Very practical, had this many times. Compiler throw ForOfStatement as reason for not optimizing. For..of has much more behind. Let look at it inside.
There is a lot of reasons shown by compiler for not optimizing, but lot of them based on some core problems. In this case we’ve got generators which are not optimizable yet.
We’ve got here simple function which runs billion times, returning concatenated number with string. Make some small difference.
Now we are running function billion + 4 times, getting 3x increased time. That’s because of stubs which I said about before.
So, for each operation compiler tries to inline how it should work. When we have only one case it’s called monomorphic stub, more - mega. Megamorphic stubs must be slower, as they need to cover more safety gates - strings are handled differently than numbers etc.
We’ve got here two examples which are just attaching values to one of properties. So why there is a difference in time?
There are different methods of representing object in memory. Hash tables - difficult objects, slow. Fast elements - objects with integer indexes, but small (<100k elements). In-object properties - creates hidden classes, are based on transitions. Methods are treat differently to optimize frequent situation. Important is that objects can move from one representation to another - e.g. when you remove some index in array it might be used as hash table.
We have to see how exactly hidden classes works. Each time property is assigned, there is another hidden class created.
Here we’ve got example in more common code - we’ve got one conditional in constructor, and 2 instances of Point. In memory they will not share some structure - first will be M2, second M3, as conditional result is different.
In Map hidden class structure, properties on end are in-object properties, extra properties are hash map properties and elements are properties for fast elements objects.
Another example of types - compiler can’t determine what type arguments is, so when you try to change value of this variable, it takes more time.
Last example - mutating arguments. As you know, arguments is magic variable in functions. It needs to be always aligned to named arguments. It causes yet problems for compiler, as it has to align it properly.
As comparison, let see some function which is mutating similar (array-like) object. It’s more than 15 times faster.
Anyway, if you want to mutate arguments, let see on other options. When we mutate named argument but read arguments object it’s still slow, but when we use only named we get proper speed. Deduction: more magic = more problems
in Chrome DevTools most important - use timeline & profiler (it will show also not optimized functions), in case of Node we’ve got many additional options, some helpful: inspect, trace-opt & trace-deopt (trace (de)optimizations), allow-natives-syntax which allow advanced scripting, —prof for profiling and —prof-process for reading log from this
That’s how looks log in node —prof-process, so you can see where to find problems; you can see there also external modules (e.g. C++)
Garbage Collector is a component which is responsible for deleting data from memory when they’re not needed. In V8 there is using Tracing garbage collector algorithm. Managing memory by yourself must be more efficient, but with GC you don’t have to think about it much. GC in V8 has stop-the-world mechanism.
Garbage collector makes graph of nodes with pointers between. All objects which are accessible from root (e.g. global or window) are live. It’s safe to make assumption that most new data are temporary, so should be earlier destroyed (e.g. counters in loop).
2 generations: young and old. When object is surviving in new space for a while it’s moved to old space. Allocation to old is fast, collecting not. New space (as said before) most of time uses temp, so it’s cleared faster, old uses slow algorithm. It’s marking objects if they are live, and sweep dead ones. Right now, marking can be processed in chunks.
Let show simplified example of collection. We start from root object and go to all its child nodes.
We checked B and it hasn’t any child nodes, but it’s reachable - mark it as black. A has nodes.
Now we’ve checked all nodes. Time to collect.
We are removing pointer from A to D which is stored in A.property.
As before we go from root to child nodes
Now we passed whole tree, where D had no pointers.
So D is swept from memory. It’s ofc simplified - in truth there are still pointers to this element till now and when we reach A pointer is removed.
New space is pretty simple, but there are other spaces in memory. We’ve got code space - code & instructions are stored, spaces like cell, map or property cell space where we store information needed for compiler job, old space splitted to 3 parts - pointer space (objects with pointers, data - raw data, large object - big objects which can’t be stored anyway is stored there.
I will tell you some common memory leaks. You could assign to global values (under root), which might be big.
Another: Timers which use some external data. You should clear it. Also applies to e.g. listeners or cached DOM elements.
More complex example. Similar happened in Meteor framework. We’ve got a function which is replacing old data with new, but using old one. There is one unused function and someMethod. Problem is that functions in same closure share it. Unused -> originalThing (which is old one), someMethod keeps referencing all the time. Because of that, each interval all stuff (with longStr) is leaked.
And now: how to detect leaks. In Chrome you can use just Profiler in DevTools, in NodeJS you can use heapdump module to dump heap. You can also import it in DevTools. Compare two or more snapshots and see difference by Comparison type.
Less often stuff - you can use general GCore program (linux). You can more programmatically search for stuff in memory by this tool.