{"id":13656569,"url":"https://github.com/davidmarkclements/v8-perf","last_synced_at":"2025-07-08T13:03:04.537Z","repository":{"id":65999950,"uuid":"97495881","full_name":"davidmarkclements/v8-perf","owner":"davidmarkclements","description":"Exploring v8 performance characteristics in Node across v8 versions 5.1, 5.8, 5.9, 6.0 and 6.1 ","archived":false,"fork":false,"pushed_at":"2017-09-07T16:59:13.000Z","size":5565,"stargazers_count":278,"open_issues_count":1,"forks_count":21,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-04-13T04:09:30.063Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidmarkclements.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-17T16:10:14.000Z","updated_at":"2024-05-23T02:35:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"19f17618-a4a0-43bc-af13-2a9904903776","html_url":"https://github.com/davidmarkclements/v8-perf","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmarkclements%2Fv8-perf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmarkclements%2Fv8-perf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmarkclements%2Fv8-perf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmarkclements%2Fv8-perf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidmarkclements","download_url":"https://codeload.github.com/davidmarkclements/v8-perf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248661704,"owners_count":21141450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T05:00:23.792Z","updated_at":"2025-04-13T04:09:33.437Z","avatar_url":"https://github.com/davidmarkclements.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# A new V8 is coming, Node.js performance is changing.\n## How the performance characteristics of V8's Turbofan will affect the way we optimize\n\nAuthors: [David Mark Clements](https://twitter.com/davidmarkclem) and\n[Matteo Collina](https://twitter.com/matteocollina)\n\nReviewers: [Franziska Hinkelmann](https://twitter.com/fhinkel) and\n[Benedikt Meurer](https://twitter.com/bmeurer) from the V8 team.\n\n**Update: Node.js 8.3.0 will [ship V8 6.0 with Turbofan](https://github.com/nodejs/node/pull/14594). Validate your\napplication with `NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/rc nvm i 8.3.0-rc.0`**\n\nSince it's inception Node.js has depended on the V8 JavaScript engine to provide\ncode execution in the language we all know and love. The V8 JavaScript engine is\na JavaScript VM written by Google for the Chrome browser. From the beginning,\na primary goal of V8 was to make JavaScript fast, or at least - faster than the competition.\nFor a highly dynamic, loosely typed language this is no easy feat.\nThis piece is about the evolution of V8 and JS engines performance.\n\nA central piece of the V8 engine that allows it to execute JavaScript at high speed is\nthe JIT (Just In Time) compiler. This is a dynamic compiler that can optimize code during\nruntime. When V8 was first built the JIT Compiler was dubbed FullCodegen.\nThen, the V8 team implemented Crankshaft, which included many\nperformance optimizations that FullCodegen did not implement.\n\n_Edit: FullCodegen was the first optimizing compiler of V8, thanks [Yang Guo](https://twitter.com/hashseed)\nfor reporting._\n\nAs an outside observer and user of JavaScript since the 90's, it has seemed that\nfast and slow paths in JavaScript (whatever the engine may be) were often counter-intuitive, the\nreasons for apparently slow JavaScript code were often difficult to fathom.\n\nIn recent years [Matteo Collina](https://twitter.com/matteocollina) and [I](https://twitter.com/davidmarkclem)\nhave focused on finding out how to write performant Node.js code. Of course this means\nknowing which approaches are fast and which approaches are slow when our code is executed by\nthe V8 JavaScript engine.\n\nNow it's time for us to challenge all our assumptions about performance, because the V8 team has\nhas written a new JIT Compiler: Turbofan.\n\nStarting with the more commonly known \"V8 Killers\" (pieces of code which\ncause optimization bail-out - a term that no longer makes sense in a Turbofan context)\nand moving towards the more obscure discoveries Matteo and I have made around Crankshaft performance,\nwe're going to walk through a series of microbenchmark results and observations over progressing versions\nof V8.\n\nOf course, before optimizing for V8 logic paths, we should first focus on API design, algorithms and data structures.\nThese microbenchmarks are meant as indicators of how JavaScript execution in Node is changing. We can use these indicators\nto influence our general code style and the ways we improve performance *after* we've applied the usual optimizations.\n\nWe'll be looking at the performance of these microbenchmarks on V8 versions 5.1, 5.8, 5.9, 6.0 and 6.1.\n\nTo put each of these versions into context: V8 5.1 is the engine used by Node 6 and uses the Crankshaft\nJIT Compiler, V8 5.8 is used in Node 8.0 to 8.2 and uses a mixture of Crankshaft *and* Turbofan.\n\nCurrently 6.0 engine is due to be in Node 8.3 and\nV8 version 6.1 is the latest version of V8 (at the time of writing) which is integrated with Node on\nthe experimental node-v8 repo at https://github.com/nodejs/node-v8. In other words, V8 version 6.1 will\neventually be in some future version of Node, likely Node.js 9.\n\nLet's take a look at our microbenchmarks and on the other side we'll talk about what this\nmeans for the future. All the microbenchmarks are executed using\n[benchmark.js](https://www.npmjs.com/package/benchmark), and the values\nplotted are operation per second, so higher is better in every diagram.\n\n### The try/catch problem\n\nOne of the more well known deoptimization patterns is use of `try/catch` blocks.\n\nIn this microbenchmark we compare four cases:\n\n* a function with a `try/catch` in it (*sum with try catch*)\n* a function without any `try/catch` (*sum without try catch*)\n* calling a function within a `try` block (*sum wrapped*)\n* simply calling a function, no `try/catch` involved (*sum function*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/try-catch.js\u003e\n\n![](graphs/try-catch-bar.png)\n\nWe can see that the existing wisdom around `try/catch` causing performance problems is true\nin Node 6 (V8 5.1) but has significantly less performance impact in Node 8.0-8.2 (V8 5.8).\n\nAlso of note, calling a function from within a `try` block is much slower than calling it from\noutside a `try` block - this holds true in both Node 6 (V8 5.1) and Node 8.0-8.2 (V8 5.8).\n\nHowever for Node 8.3+ the performance hit of calling a function inside a `try` block is negligible.\n\nNevertheless, don't rest too easy. While working on some performance workshop material,\nMatteo and I [found a performance bug](https://bugs.chromium.org/p/v8/issues/detail?id=6576\u0026q=matteo%20collina\u0026colspec=ID%20Type%20Status%20Priority%20Owner%20Summary%20HW%20OS%20Component%20Stars), where a rather specific combination\nof circumstances can lead to an infinite deoptimization/reoptimization\ncycle in Turbofan (this would count as a \"killer\" - a pattern that destroys performance).\n\n### Removing properties from objects\n\nFor years now, `delete` has been off limits to anyone wishing to write high performance\nJavaScript (well at least where we're trying to write optimal code for a hot path).\n\nThe problem with `delete` boils down to the way V8 handles the dynamic nature of JavaScript\nobjects and the (also potentially dynamic) prototype chains that make property lookups\neven more complex at an implementation level.\n\nThe V8 engine's technique for making property objects high speed is to create a class in the C++\nlayer based on the \"shape\" of the object. The shape is essentially what keys and values a property\nhas (including the prototype chain keys and values). These are known as \"hidden classes\". However,\nthis is an optimization that occurs on objects at runtime, if there's uncertainty about the shape\nof the object V8 has another mode for property retrieval: hash table lookup. The hash table lookup is\nsignificantly slower. Historically, when we `delete` a key from the object subsequent property access\nwill be a hash table look up. This is why we avoid `delete` and instead set properties to `undefined`\nwhich leads to the same result as far as values are concerned but may be problematic when checking for\nproperty existence; however it's often good enough for pre-serialization redaction because `JSON.stringify`\ndoes not include `undefined` values in its output (`undefined` isn't a valid value in the JSON specification).\n\nNow, let's see if the newer Turbofan implementation addresses the `delete` problem.\n\nIn this microbenchmark we compare three cases:\n\n* serializing an object after an object's property has been set to `undefined`\n* serializing an object after `delete` has been used to remove an object's property\n  which was not the last property added.\n* serializing an object after `delete` has been used to remove the object's\n  most recently added property.\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/property-removal.js\u003e\n\n![](graphs/property-removal-bar.png)\n\nIn V8 6.0 and 6.1 (not yet used in any Node releases), deleting the last property added to an object hits a fast path in V8,\nand thus it is faster, even, than setting to `undefined`. This is excellent news, as it shows that the V8 team is working\ntowards improving the performance of `delete`.\nHowever, the `delete` operator will still result in a significant performance hit on property access if a property\n*other than the most recently added property* is deleted from the object. So overall we have to continue to recommend against using `delete`.\n\n_Edit: in a previous version of the post we concluded that `delete` could and should be used \nin future Node.js releases. However [Jakob\nKummerow](http://disq.us/p/1kvomfk) let us know that our benchmarks only\ntriggered the last property accessed case. Thanks [Jakob\nKummerow](http://disq.us/p/1kvomfk)!_\n\n### Leaking and arrayifying `arguments`\n\nA common problem with the implicit `arguments` object available to normal JavaScript functions (as opposed to\nfat arrow functions which do not have `arguments` objects) is that it's array-like but *not* an array.\n\nIn order to use array methods or most array behavior, the indexing properties of the `arguments` object have\nbe copied to an array. In the past JavaScripters have had a propensity towards equating *less code* with\n*faster* code. Whilst this rule of thumb yields a payload-size benefit for browser-side code, the same rule\ncan cause pain on the server side where code size is far less important than execution speed. So a seductively terse\nway to convert the `arguments` object to an array became quite popular: `Array.prototype.slice.call(arguments)`.\nThis calls the Array `slice` method passing the `arguments` object as the `this` context of that method,\nthe `slice` method sees an object that quacks like an array and acts accordingly. That is, it takes a slice of\nthe entire arguments array-like object as an array.\n\nHowever when a function's implicit `arguments` object is exposed from the functions context\n(for instance, when it's returned from the function or passed into another function as in the case of\n`Array.prototype.slice.call(arguments)`) this typically causes performance degradation. Now it's time\nto challenge that assumption.\n\nThis next microbenchmark measures two interrelated topics across our four V8 versions: the cost of leaking `arguments`\nand the cost of copying arguments into an array (which is subsequently exposed from the function scope in place of the `arguments` object).\n\nHere's our cases in detail:\n\n* Exposing the `arguments` object to another function - no array conversion (*leaky arguments*)\n* Making a copy of the `arguments` object using the `Array.prototype.slice` tricks (*Array prototype.slice arguments*)\n* Using a for loop and copying each property across (*for-loop copy arguments*)\n* Using the EcmaScript 2015 spread operator to assign an array of inputs to a reference (*spread operator*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/arguments.js\u003e\n\n![](graphs/arguments-bar.png)\n\nLet's take a look at the same data in line graph form to emphasise the change in performance characteristics:\n\n![](graphs/arguments-line.png)\n\nHere's the takeaway: if we want to write performant code around processing function inputs as an array\n(which in my experience seems fairly common), then in Node 8.3 and up we should use the spread operator.\nIn Node 8.2 and down we should use a for loop to copy the keys from `arguments` into a new (pre-allocated)\narray (see the benchmark code for details).\n\nFurther in Node 8.3+ we won't be punished for exposing the `arguments` object to other functions,\nso there may be further performance benefits in cases where we don't need a full array and can make do\nwith an array-like structure.\n\n### Partial application (currying) and binding\n\nPartial application (or currying) refers to the way we can capture state in nested closure scopes.\n\nFor instance:\n\n```js\nfunction add (a, b) {\n  return a + b\n}\nconst add10 = function (n) {\n  return add(10, n)\n}\nconsole.log(add10(20))\n```\n\nHere the `a` parameter of `add` is partially applied as the value `10` in the `add10` function.\n\nA terser form of partial application was made available with the `bind` method since EcmaScript 5:\n\n```js\nfunction add (a, b) {\n  return a + b\n}\nconst add10 = add.bind(null, 10)\nconsole.log(add10(20))\n```\n\nHowever, we would typically not use `bind` because is demonstrably slower than using a closure.\n\nThis benchmark measures the difference between `bind` and closure over our target V8 versions,\nwith direct function calls as the control.\n\nHere's our four cases:\n\n* a function that calls another function with the first argument partially applied (*curry*)\n* a fat arrow function that calls another function with the first argument partially applied (*fat arrow curry*)\n* a function that is created via `bind` that partially applies the first argument of another function (*bind*)\n* a direct call to a function without any partial application (*direct call*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/currying.js\u003e\n\n![](graphs/currying-line.png)\n\nThe line graph visualization of this benchmark's results clearly illustrates how the\napproaches converge in later versions of V8. Interestingly, partial application\nusing fat arrow functions is *significantly* faster than using normal functions (at\nleast in our microbenchmark case). In fact it almost traces the performance profile\nof making a direct call. In V8 5.1 (Node 6) and 5.8 (Node 8.0-8.2) `bind` is\nvery slow by comparison, and it seems clear that using fat arrows for partial application\nis the fastest option. However `bind` speeds up by an order of magnitude\nfrom V8 version 5.9 (Node 8.3+) becoming the fastest approach (by an almost negligible amount)\nin version 6.1 (future Node).\n\nThe fastest approach to currying over all versions is using fat arrow functions. Code using\nfat arrow functions in later versions will be as close to using `bind` as makes no odds, and\ncurrently it's faster than using normal functions. However, as a caveat, we probably need to investigate more types\nof partial application with differently sized data structures to get a fuller picture.\n\n### Function character count\n\nThe size of a function, including it's signature, the white space and even comments can affect\nwhether the function can be inlined by V8 or not. Yes: adding a comment to your function\nmay cause a performance hit somewhere in the range of a 10% speed reduction. Will this change with Turbofan? Let's find out.\n\nIn this benchmark we look at three scenarios:\n\n* a call to a small function (*sum small function*)\n* the operations of a small function performed inline, padded out with comments (*long all together*)\n* a call to a big function that has been padded with comments (*sum long function*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/function-size.js\u003e\n\n![](graphs/function-size-bar.png)\n\nIn V8 5.1 (Node 6) *sum small function* and *long all together* are equal. This perfectly illustrates how\ninlining works. When we're calling the small function, it's as if V8 writes the contents of the\nsmall function into the place it's called from. So when we actually write the contents of a function\n(even with the extra comment padding), we've manually inlined those operations and the performance is\nequivalent. Again we can see in V8 5.1 (Node 6) that calling a function that is padded with comments which\ntake it past a certain size, leads to much slower execution.\n\nIn Node 8.0-8.2 (V8 5.8) the situation is pretty much the same, except the cost of calling the small\nfunction has noticeably increased; this may be due to the smushing together of Crankshaft and Turbofan\nelements whereas one function may be in Crankshaft the other may be in Turbofan causing disjoints in\ninlining abilities (i.e. there has to be a jump between clusters of serially inlined functions).\n\nIn 5.9 and upwards (Node 8.3+), any size added by irrelevant characters such as whitespace or comments\nhas no bearing on the functions performance.\nThis is because Turbofan uses the functions AST ([Abstract Syntax\nTree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) node count to\ndetermine function size, rather than using character count as in\nCrankshaft. Instead of checking byte count of a function, it consider\nthe actual instructions of the function, so that from V8 5.9 (Node 8.3+)\n**whitespace, variable name character count, function signatures and\ncomments no longer factors in whether a function will inline.**\n\nNotably, again, we see that overall performance of functions decreases.\n\nThe takeaway here should still be to keep functions small. At the moment we still have to avoid\nover-commenting (and even whitespace) inside functions. Also if you want the absolute fastest speed,\nmanually inlining (removing the call) is consistently the fastest approach. Of course this has to be balanced\nagainst the fact that after a certain size (of actual executable code) a function won't be inlined, so copy-pasting\ncode from other functions into your function could cause performance problem. In other words manual\ninlining is a potential footgun; it's better to leave inlining up to the compiler in most cases.\n\n### 32-bit integers vs integers stored in doubles\n\nIt's rather well known that JavaScript only has one number type: `Number`.\n\nHowever, V8 is implemented in C++ so a choice has to be made\non the underlying type for a numeric JavaScript value.\n\nIn the case of integers (that is, when we specify a number in JS without a decimal), V8 assumes that\nall numbers fit into 32 bits - until they don’t. This seems like a fair choice, since in many cases a number\nis within the -2147483648 - 2147483647 range. If a JavaScript (whole) number exceeds 2147483647\nthe JIT Compiler has to dynamically alter the underlying type for the number to a double\n(a double-precision floating point number) - this may also have potential knock on effects with\nregards to other optimizations.\n\nThis benchmark looks at three cases:\n\n* a function handling only numbers in the 32-bit range (*sum small*)\n* a function handling a combination of 32-bit and double numbers (*from small to big*)\n* a function handling only double numbers (*all big*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/numbers.js\u003e\n\n![](graphs/numbers-line.png)\n\nWe can see from the graph that whether it's Node 6 (V8 5.1) or Node 8 (V8 5.8)\nor even some future version of Node this observation holds true. Operations using Numbers\n(integers) greater than 2147483647 will cause functions to run between a half and two thirds of the speed.\nSo, if you have long numeric ID's - put them in strings.\n\nIt's also quite noticeable that operations with numbers in the 32-bit range have a speed increase\nbetween Node 6 (V8 5.1) and Node 8.1 and 8.2 (V8 5.8) but slow significantly in Node 8.3+ (V8 5.9+).\nHowever, operations over double numbers become faster in Node 8.3+ (V8 5.9+).\nIt's likely that this genuinely is a slow-down in (32-bit) number handling rather than being related\nto the speed of function calls or `for` loops (which are used in the benchmark code).\n\n_Edit: updated thanks to [Jakob Kummerow](http://disq.us/p/1kvomfk) and\n[Yang Guo](https://twitter.com/hashseed) and the V8 team, for both accuracy and precision\nof the results._\n\n### Iterating over objects\n\nGrabbing all of an object's values and doing something with them is a common task\nand there are many ways to approach this. Let's find out which is fastest across\nour V8 (and Node) versions.\n\nThis benchmark measures four cases for all V8 versions benched:\n\n* using a `for`-`in` loop with a `hasOwnProperty` check to get an object's values (*for in*)\n* using `Object.keys` and iterating over the keys using the Array `reduce` method, accessing the object values\ninside the iterator function supplied to `reduce` (*Object.keys functional*)\n* using `Object.keys` and iterating over the keys using the Array `reduce` method, accessing the object values\ninside the iterator function supplied to `reduce` where the iterator function is a fat arrow function (*Object.keys functional with arrow*)\n* looping over the array returned from `Object.keys` with a `for` loop, accessing the object values within the loop (*Object.keys with for loop*)\n\nWe also benchmarks an additional three cases for V8 5.8, 5.9, 6.0 and 6.1\n\n* using `Object.values` and iterating over the values using the Array `reduce` method, (*Object.values functional*)\n* using `Object.values` and iterating over the values using the Array `reduce` method, where the iterator function\nsupplied to `reduce` is a fat arrow function (*Object.values functional with arrow*)\n* looping over the array returned from `Object.values` with a `for` loop (*Object.values with for loop*)\n\nWe don't bench these cases in V8 5.1 (Node 6) because it doesn't support the native EcmaScript 2017 `Object.values` method.\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/object-iteration.js\u003e\n\n![](graphs/object-iteration-line.png)\n\nIn Node 6 (V8 5.1) and Node 8.0-8.2 (V8 5.8) using `for`-`in` is by far the fastest way to loop over an object's keys,\nthen access the values of the object. At roughly 40 million operations per second, it's 5 times faster than\nthe next closest approach which is `Object.keys` at around 8 million op/s.\n\nIn V8 6.0 (Node 8.3) something happens to `for`-`in` and it cuts down to one quarter the speed of\nformer versions, but is still faster than any other approach.\n\nIn V8 6.1 (the future of Node), the speed of `Object.keys` leaps forward and becomes faster than\nusing `for`-`in` - but no where near the speed of `for`-`in` in V8 5.1 and 5.8 (Node 6, Node 8.0-8.2).\n\nA driving principle behind Turbofan seems to be to optimize for intuitive coding behavior. That is,\noptimize for the case that is most ergonomic for the developer.\n\nUsing `Object.values` to get values directly is slower than using `Object.keys` and accessing\nthe values in the object. On top of that, procedural loops remain faster than functional programming.\nSo there may be some more work to do when it comes to iterating over objects.\n\nAlso, for those who've used `for`-`in` for its performance benefits it's going to be a painful\nmoment when we lose a large chunk of speed with no alternative approach available.\n\n_Note: the `for`-ìn` loops performance has been fixed in V8, see\nhttp://benediktmeurer.de/2017/09/07/restoring-for-in-peak-performance/\nfor more details. This change will likely be integrated by Node 9._\n\n### Object allocation\n\nWe allocate objects *all the time* so this is a great area to measure.\n\nWe're going to look at three cases:\n\n* allocating objects using object literals (*literal*)\n* allocating objects from an EcmaScript 2015 Class (*class*)\n* allocating objects from a constructor function (*constructor*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/object-creation.js\u003e\n\n![](graphs/object-creation-bar.png)\n\nObject allocation have the same cost in all the test V8 versions, apart\nfrom classes in Node 8.2 (V8 5.8), which are slower than the rest. This\nis due to the mixed Crankshaft/Turbofan nature of V8 5.8, and it will be\nresolved in Node 8.3 with V8 6.0.\n\n_Edit: Jakob Kummerow noted in [http://disq.us/p/1kvomfk](http://disq.us/p/1kvomfk) that Turbofan\ncan optimize away the object allocation in this specific microbenchmark,\nleading to incorrect results so this article has been edited accordingly._\n\n#### Object allocation elimination\n\nWhile collating our results for this article, we came across a really\nneat optimization that Turbofan brings to a certain category of\nobject allocation. Originally we mistook this for all object allocation, but\nthanks to input from the V8 team we've come to understand the circumstances\nwhich this optimization relates to.\n\nIn the previous **Object allocation** benchmarks we assign a variable, set it\nto `null` and then reassign the variable a bunch of times to avoid triggering\nthe special optimization case which we're going to look at now.\n\nIn this benchmark we look at three cases:\n\n* allocating objects using object literals (*literal*)\n* allocating objects from an EcmaScript 2015 Class (*class*)\n* allocating objects from a constructor function (*constructor*)\n\nHowever the difference is, the reference to the object is not overwritten\nwith additional object allocations, and the object is passed to another function\nwhich performs a task on the object.\n\nLet's take a look at the results!\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/object-creation-inlining.js\u003e\n\n![](graphs/hardcoded-object-creation-bar.png)\n\nNotice how V8 6.0 (Node 8.3) and 6.1 (Node 9) yield a massive jump in speed for this case,\nover 500 million operations per second, mainly because nothing is really happening once\nTurbofan applies the optimization. In this particular scenario, Turbofan is able to\n*optimize away* the object allocation because it's able to determine that subsequent \nlogic can be executed without requiring the existence of the actual object.\n\nThe benchmark code still doesn't fully demonstrate what it takes to trigger this\noptimization (because it's more about what's not there than what is) and the conditions\nit takes for this optimization to applied are complex.  \n\nHowever one condition which we know will definitely not allow for the object to be optimized away by Turbofan is as follows: \n\nThe object must not outlive the function it was created in. That means, there should be\nno reference to that object after every function from that point in the stack has completed. The object\n*can* be passed to other functions, but if we add that object to a `this` context, or assign it\nto an outer scoped variable, or add it to another object that lives on after the stack has finished the optimization cannot be applied.\n\nThe ramifications for this could be pretty cool, however it's difficult to predict all the\nconditions where such an optimization occurs. Nevertheless, it may lead yield useful speedups\nwhen the complex set of conditions are met.\n\n_Edit: Thanks to Jakob Kummerow and the rest of the V8 team for helping us discover the underlying reasons for this particular behavior.\nAs part of this research we discovered a performance regression in the new GC of V8, Orinoco.\nif you are interested check out\n\u003chttps://v8project.blogspot.it/2016/04/jank-busters-part-two-orinoco.html\u003e and \u003chttps://bugs.chromium.org/p/v8/issues/detail?id=6663\u003e_\n\n### Polymorphic vs monomorphic code\n\nWhen we always pass the same type of argument into a function (say, we always pass a string), we are using that function in a monomorphic way.\n\nSome functions are written to be polymorphic. We can think of a polymorphic function as a function\nthat accepts different types *in the same argument position*. For instance a function may accept \neither a string or an object as its first argument. However in this context when we say \"type\", we don't just mean string versus number versus object, we mean *object shape* (although JavaScript types \nactually count as different object shapes as well). \n\nThe shape of an object is defined by its properties and values. For instance,\nin the following snippet, `obj1` and `obj2` are the same shape but `obj3` and `obj4` are different\nshapes to the rest:\n\n```js\nconst obj1 = { a: 1 }\nconst obj2 = { a: 5 }\nconst obj3 = { a: 1, b: 2 }\nconst obj4 = { b: 2 }\n```\n\nProcessing objects with different shapes through the same code can make for nice interfaces in some circumstances but tends to have a negative impact on performance.\n\nLet's see how monomorphic and polymorphic cases do in our benchmarks.\n\nHere we investigate two cases:\n\n* a function where we process objects with different properties (`polymorphic`)\n* a function where we process objects with the same properties (`monomorphic`)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/polymorphic.js\u003e\n\n![](graphs/polymorphic-bar.png)\n\nThe data visualized in our graph shows conclusively that monomorphic functions outperform polymorphic functions across all V8 versions tested. However, the performance of the polymorphic function improves from V8 5.9+ (which means it improves from Node 8.3 which uses V8 6.0).\n\nPolymorphic functions are very common through the Node.js codebase, and\nthey provide a great deal flexibility through the APIs. Thanks to this improvement \naround polymorphic interaction, we may see a degree of improved\nperformance in more complex Node.js applications.\n\nIf we're writing code that needs to be optimal, that is a function that will be called many times over,\nthen we should commit to call functions with the parameters with the same \"shape\".\nOn the other hand, if a function is only called once or twice, say an\ninstantiating/setup function, then a polymorphic API is acceptable.\n\n_Edit:  Thanks [Jakob Kummerow](https://github.com/davidmarkclements/v8-perf/issues/9#issuecomment-318796286)\nfor providing a more reliable version of this benchmark._\n\n### The `debugger` keyword\n\nFinally, let's talk about the `debugger` keyword.\n\nBe sure to strip `debugger` statements from your code. Stray `debugger` statements destroy performance.\n\nWe're looking at two cases:\n\n* a function that contains the `debugger` keyword (*with debugger*)\n* a function that does not contain the `debugger` keyword (*without debugger*)\n\n**Code:** \u003chttps://github.com/davidmarkclements/v8-perf/blob/master/bench/debugger.js\u003e\n\n![](graphs/debugger-line.png)\n\nYep. Just the presence of the `debugger` keyword is terrible for performance\nacross all V8 versions tested.\n\nThe *without debugger* line noticeably drops over successive V8 versions, we'll talk about this in the [Summary](#summary).\n\n### A real world benchmark: Logger comparison\n\nIn addition to our microbenchmarks we can take a look at the holistic effects of our\nV8 versions by using benchmarks of most popular loggers for Node.js that Matteo and I\nput together while we were creating [Pino](http://getpino.io).\n\nThe following bar chart represent the time taken to log 10 thousands\nlines (lower is better) of the most popular loggers in Node.js 6.11 (Crankshaft):\n\n![](graphs/loggers-updated.png)\n\nWhile the following is the same benchmarks using V8 6.1 (Turbofan):\n\n![](graphs/loggers-turbofan.png)\n\nWhile all of the logger benchmarks improve in speed (by roughly 2x), the Winston logger derives the\nmost benefit from the new Turbofan JIT compiler. This seems to demonstrate the speed convergence\nwe see among various approaches in our microbenchmarks: the slower approaches in Crankshaft\nare significantly faster in Turbofan while the fast approaches in Crankshaft tend get a little slower Turbofan.\nWinston, being the slowest, is likely using the approaches which are slower in Crankshaft but much faster\nin Turbofan whereas Pino is optimized to use the fastest Crankshaft approaches. While a speed increase\nis observed in Pino, it's to a much lesser degree.\n\n### Summary\n\nSome of the benchmarks show that while slow cases in V8 5.1, V8 5.8 and 5.9 become faster with the advent of full Turbofan enablement\nin V8 6.0 and V8 6.1, the fast cases also slow down, often matching the increased speed of the slow cases.\n\nMuch of this is due to the cost of making function calls in Turbofan (V8 6.0 and up). The idea behind\nTurbofan was to optimize for common cases and eliminate commonly used \"V8 Killers\". This has resulted in a net performance benefit for (Chrome) browser and\nserver (Node) applications. The trade-off appears to be (at least initially) a speed decrease\nfor the most performant cases. Our logger benchmark comparison indicates that the general net effect\nof Turbofan characteristics is comprehensive performance improvements even across significantly\ncontrasting code bases (e.g. Winston vs Pino).\n\nIf you've had an eye on JavaScript performance for a while, and adapted coding behaviors to the quirks\nof the underlying engine it's nearly time to unlearn some techniques. If you've focused on best practices,\nwriting generally *good* JavaScript then well done, thanks to the V8 team's tireless efforts,\na performance reward is coming.\n\n---\n\nThe raw data for this article can be found at: https://docs.google.com/spreadsheets/d/1mDt4jDpN_Am7uckBbnxltjROI9hSu6crf9tOa2YnSog/edit?usp=sharing\n\nMost of the microbenchmarks were taken on a Macbook Pro 2016, 3.3 GHz Intel Core i7 with 16 GB 2133 MHz LPDDR3,\nothers (numbers, property removal, polymorphic, object creation) were taken on a MacBook Pro 2014,\n3 GHz Intel Core i7 with 16 GB 1600 MHz DDR3. All the measurements\nbetween the different Node.js versions were taken on the same machine.\nWe took great care in assuring that no other programs were interfering.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmarkclements%2Fv8-perf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidmarkclements%2Fv8-perf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmarkclements%2Fv8-perf/lists"}