Object.prototype.toString()
in the V8 engine. In particular, it’s about why this construct is important, how it has changed with the advent of ES2015 symbols, and the optimization approach that Mozilla engineers have suggested, which resulted in an approximately sixfold increase in performance of toString()
in V8.toString
method: class A { get [Symbol.toStringTag]() { return 'A'; } } Object.prototype.toString.call(''); // "[object String]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call(new A); // "[object A]"
Object.prototype.toString()
for ES2015 and later versions of the standard first converts its this value to an object using the abstract operation ToObject , and then searches for Symbol.toStringTag
in the resulting object and in its prototype chain. This is what can be found in the relevant part of the language specification:@@toStringTag
(this is a special internal syntax of the language specification for a well-known symbol called toStringTag
). Adding the Symbol.toStringTag
construction to ES2015 significantly extends the capabilities of developers, but at the same time means a certain amount of resources.Object.prototype.toString()
method in Chrome and Node.js has already been investigated , as this method is extensively used for type checking with some popular frameworks and libraries. For example , the AngularJS framework uses this method to implement various support functions, including angular.isDate , angular.isArrayBuffer , and angular.isRegExp . For example: /** * @ngdoc function * @name angular.isDate * @module ng * @kind function * * @description * Determines if a value is a date. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Date`. */ function isDate(value) { return toString.call(value) === '[object Date]'; }
Object.prototype.toString()
to implement value checks. So, for example, the predicates _.isPlainObject and _.isDate from lodash are arranged: /** * Checks if `value` is classified as a `Date` object. * * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a date object, else `false`. * @example * * isDate(new Date) * // => true * * isDate('Mon April 23 2012') * // => false */ function isDate(value) { return isObjectLike(value) && baseGetTag(value) == '[object Date]' }
Symbol.toStringTag
search Symbol.toStringTag
in Object.prototype.toString()
is a bottleneck in real-world application performance. This conclusion was made during the study of the benchmark Speedometer . By running only the AngularJS subtest from Speedometer using the V8 internal profiler (in order to enable it, you need to pass the command line key --no-sandbox --js-flags=--prof
when you start Chrome), we found that most of the time is spent on executing the @@toStringTag
search (in GetPropertyStub
) and on executing the ObjectProtoToString
code, which implements the built-in Object.prototype.toString()
method:Object.prototype.toString()
performance in arrays: function f() { var res = ""; var a = [1, 2, 3]; var toString = Object.prototype.toString; var t = new Date; for (var i = 0; i < 5000000; i++) res = toString.call(a); print(new Date - t); return res; } f();
--prof
command line --prof
) has already shown the essence of the problem. The main resources are spent on searching Symbol.toStringTag
in the array [1, 2, 3]
. Approximately 73% of the total execution time is spent on a non-resultant property search (in the GetPropertyStub
function, which implements a universal property search), another 3% is spent in the built-in function ToObject
, which, in the case of arrays, is an empty operation (arrays, from the point of view JavaScript are already objects).@@toStringTag
or @@toPrimitive
. Thanks to this approach, the resource-intensive Symbol.toStringTag
search can, in general, be avoided, since this search does not produce results anyway. The implementation of this proposal led to an approximately twofold increase in the microbench performance with an array for SpiderMonkey.Symbol.toStringTag
to only Symbol.toStringTag
and Object.prototype.toString()
. The fact is that I did not find (have not yet found) evidence that Symbol.toPrimitive
is an important source of trouble in Chrome or Node.js. The basic idea here is that, by default, we suppose that instances of objects do not have interesting characters , and each time we add a new property to an instance, we check whether the name of this property is a similar symbol. If so, we set a specific bit in the hidden object instance classes. const obj = {}; Object.prototype.toString.call(obj); // obj[Symbol.toStringTag] = 'a'; Object.prototype.toString.call(obj); //
obj
begins its existence without possessing an interesting symbol . Therefore, the Object.prototype.toString()
call follows a new, fast execution path when you can skip the Symbol.toStringTag
search (this is also because the Object.prototype
also does not have an interesting symbol ). The second call performs the usual slow search operation, since obj
now has an interesting character .GetPropertyStub
. Instead, the built-in method Object.prototype.toString()
creates, as expected, the main load on the system.Object.prototype.toString()
, different values are transferred, including primitives and objects that have the specially set Symbol.toStringTag
property. As a result, the latest version of V8 was 6.5 times faster than V8 6.1.Object.prototype.toString()
, still have the potential for further optimization. In particular, the optimization described above can improve performance up to 6.5 times. You can make sure of this if you go deep enough into the results of various performance tests, such as the AngularJS subtest from the Speedometer benchmark.JavaScriptCore
, the JavaScript engine used by WebKit , caches the results of consecutive Object.prototype.toString()
calls to the hidden object instance class (this cache appeared at the beginning of 2012, before the ES2015 specification was released). This is a very interesting strategy, but its scope is limited (that is, it is useless when applied to other well-known symbols such as Symbol.toPrimitive or Symbol.hasInstance ). In addition, it requires a very complex logic of cache invalidation to ensure a timely response to changes in the prototype chain. That is why, at least for the moment, I made the choice not to favor the solution for the V8 based on the cache.Source: https://habr.com/ru/post/336306/
All Articles