Performance with JavaScript String Objects

This article aims to take a look at the performance of JavaScript engines towards primitive value Strings and Object Strings. It is a showcase of benchmarks related to the excellent article by Kiro Risk, The Wrapper Object. Before proceeding, I would suggest visiting Kiro’s page first as an introduction to this topic.

The ECMAScript 5.1 Language Specification (PDF link) states at paragraph 4.3.18 about the String object:

String object member of the Object type that is an instance of the standard built-in String constructor

NOTE A String object is created by using the String constructor in a new expression, supplying a String value as an argument.
The resulting object has an internal property whose value is the String value. A String object can be coerced to a String value
by calling the String constructor as a function (15.5.1).

and David Flanagan’s great book “JavaScript: The Definitive Guide”, very meticulously describes the Wrapper Objects at section 3.6:

Strings are not objects, though, so why do they have properties? Whenever you try to refer to a property of a string s, JavaScript converts the string value to an object as if by calling new String(s). […] Once the property has been resolved, the newly created object is discarded. (Implementations are not required to actually create and discard this transient object: they must behave as if they do, however.)

It is important to note the text in bold above. Basically, the different ways a new String object is created are implementation specific. As such, an obvious question one could ask is “since a primitive value String must be coerced to a String Object when trying to access a property, for example str.length, would it be faster if instead we had declared the variable as String Object?”. In other words, could declaring a variable as a String Object, ie var str = new String("hello"), rather than as a primitive value String, ie var str = "hello" potentially save the JS engine from having to create a new String Object on the fly so as to access its properties?

Those who deal with the implementation of ECMAScript standards to JS engines already know the answer, but it’s worth having a deeper look at the common suggestion “Do not create numbers or strings using the ‘new’ operator”.

Our showcase and objective

For our showcase, we will use mainly Firefox and Chrome; the results, though, would be similar if we chose any other web browser, as we are focusing not on a speed comparison between two different browser engines, but at a speed comparison between two different versions of the source code on each browser (one version having a primitive value string, and the other a String Object). In addition, we are interested in how the same cases compare in speed to subsequent versions of the same browser. The first sample of benchmarks was collected on the same machine, and then other machines with a different OS/hardware specs were added in order to validate the speed numbers.

The scenario

For the benchmarks, the case is rather simple; we declare two string variables, one as a primitive value string and the other as an Object String, both of which have the same value:

  var strprimitive = "Hello";
  var strobject    = new String("Hello");

and then we perform the same kind of tasks on them. (notice that in the jsperf pages strprimitive = str1, and strobject = str2)

1. length property

  var i = strprimitive.length;
  var k = strobject.length;

If we assume that during runtime the wrapper object created from the primitive value string strprimitive, is treated equally with the object string strobject by the JavaScript engine in terms of performance, then we should expect to see the same latency while trying to access each variable’s length property. Yet, as we can see in the following bar chart, accessing the length property is a lot faster on the primitive value string strprimitive, than in the object string strobject.


(Primitive value string vs Wrapper Object String – length, on jsPerf)

Actually, on Chrome 24.0.1285 calling strprimitive.length is 2.5x faster than calling strobject.length, and on Firefox 17 it is about 2x faster (but having more operations per second). Consequently, we realize that the corresponding browser JavaScript engines apply some “short paths” to access the length property when dealing with primitive string values, with special code blocks for each case.

In the SpiderMonkey JS engine, for example, the pseudo-code that deals with the “get property” operation looks something like the following:

  // direct check for the "length" property
  if (typeof(value) == "string" && property == "length") {
    return StringLength(value);
  }
  // generalized code form for properties
  object = ToObject(value);
  return InternalGetProperty(object, property);

Thus, when you request a property on a string primitive, and the property name is “length”, the engine immediately just returns its length, avoiding the full property lookup as well as the temporary wrapper object creation. Unless we add a property/method to the String.prototype requesting |this|, like so:

  String.prototype.getThis = function () { return this; }
  console.log("hello".getThis());

then no wrapper object will be created when accessing the String.prototype methods, as for example String.prototype.valueOf(). Each JS engine has embedded similar optimizations in order to produce faster results.

2. charAt() method

  var i = strprimitive.charAt(0);
  var k = strobject["0"];


(Primitive value string vs Wrapper Object String – charAt(), on jsPerf)

This benchmark clearly verifies the previous statement, as we can see that getting the value of the first string character in Firefox 20 is substiantially faster in strprimitive than in strobject, about x70 times of increased performance. Similar results apply to other browsers as well, though at different speeds. Also, notice the differences between incremental Firefox versions; this is just another indicator of how small code variations can affect the JS engine’s speed for certain runtime calls.

3. indexOf() method

  var i = strprimitive.indexOf("e");
  var k = strobject.indexOf("e");


(Primitive value string vs Wrapper Object String – IndexOf(), on jsPerf)

Similarly in this case, we can see that the primitive value string strprimitive can be used in more operations than strobject. In addition, the JS engine differences in sequential browser versions produce a variety of measurements.

4. match() method

Since there are similar results here too, to save some space, you can click the source link to view the benchmark.

(Primitive value string vs Wrapper Object String – match(), on jsPerf)

5. replace() method

(Primitive value string vs Wrapper Object String – replace(), on jsPerf)

6. toUpperCase() method

(Primitive value string vs Wrapper Object String – toUpperCase(), on jsPerf)

7. valueOf() method

  var i = strprimitive.valueOf();
  var k = strobject.valueOf();

At this point it starts to get more interesting. So, what happens when we try to call the most common method of a string, it’s valueOf()? It seems like most browsers have a mechanism to determine whether it’s a primitive value string or an Object String, thus using a much faster way to get its value; surprizingly enough Firefox versions up to v20, seem to favour the Object String method call of strobject, with a 7x increased speed.


(Primitive value string vs Wrapper Object String – valueOf(), on jsPerf)

It’s also worth mentioning that Chrome 22.0.1229 seems to have favoured too the Object String, while in version 23.0.1271 a new way to get the content of primitive value strings has been implemented.

A simpler way to run this benchmark in your browser’s console is described in the comment of the jsperf page.

8. Adding two strings

  var i = strprimitive + " there";
  var k = strobject + " there";


(Primitive string vs Wrapper Object String – get str value, on jsPerf)

Let’s now try and add the two strings with a primitive value string. As the chart shows, both Firefox and Chrome present a 2.8x and 2x increased speed in favour of strprimitive, as compared with adding the Object string strobject with another string value.

9. Adding two strings with valueOf()

  var i = strprimitive.valueOf() + " there";
  var k = strobject.valueOf() + " there";


(Primitive string vs Wrapper Object String – str valueOf, on jsPerf)

Here we can see again that Firefox favours the strobject.valueOf(), since for strprimitive.valueOf() it moves up the inheritance tree and consequently creates a new wapper object for strprimitive. The effect this chained way of events has on the performance can also be seen in the next case.

10. for-in wrapper object

  var i = "";
  for (var temp in strprimitive) { i += strprimitive[temp]; }
 
  var k = "";
  for (var temp in strobject) { k += strobject[temp]; }

This benchmark will incrementally construct the string’s value through a loop to another variable. In the for-in loop, the expression to be evaluated is normally an object, but if the expression is a primitive value, then this value gets coerced to its equivalent wrapper object. Of course, this is not a recommended method to get the value of a string, but it is one of the many ways a wrapper object can be created, and thus it is worth mentioning.


(Primitive string vs Wrapper Object String – Properties, on jsPerf)

As expected, Chrome seems to favour the primitive value string strprimitive, while Firefox and Safari seem to favour the object string strobject. In case this seems much typical, let’s move on the last benchmark.

11. Adding two strings with an Object String

  var str3 = new String(" there");
 
  var i = strprimitive + str3;
  var k = strobject + str3;


(Primitive string vs Wrapper Object String – 2 str values, on jsPerf)

In the previous examples, we have seen that Firefox versions offer better performance if our initial string is an Object String, like strobject, and thus it would be seem normal to expect the same when adding strobject with another object string, which is basically the same thing. It is worth noticing, though, that when adding a string with an Object String, it’s actually quite faster in Firefox if we use strprimitive instead of strobject. This proves once more how source code variations, like a patch to a bug, lead to different benchmark numbers.

Conclusion

Based on the benchmarks described above, we have seen a number of ways about how subtle differences in our string declarations can produce a series of different performance results. It is recommended that you continue to declare your string variables as you normally do, unless there is a very specific reason for you to create instances of the String Object. Also, note that a browser’s overall performance, particularly when dealing with the DOM, is not only based on the page’s JS performance; there is a lot more in a browser than its JS engine.

Feedback comments are much appreciated. Thanks :-)

About Panagiotis Tsalaportas

Panagiotis is a Mozilla community member, and contributor to the Mozilla Developer Network and Firefox.

More articles by Panagiotis Tsalaportas…


31 comments

  1. Nicholas C. Zakas

    Great article. I’d like to make a suggestion just in the interest of making it more readable: if you could rename str1 and str2 to strprimitive and strobject, it would be a lot easier to understand. I found myself scrolling back up frequently to double check what each variable represented.

    December 5th, 2012 at 15:26

    1. Panagiotis Tsalaportas

      Thank you so much Nicholas! :-)
      I’ve applied the changes as suggested.
      Your articles have always been a great resource on JavaScript!

      December 6th, 2012 at 08:33

  2. Felipe N. Moura

    Great article!
    I knew that primitive objects would be faster, but never really understood why!
    Good numbers and tests, and nice subject!

    I created another test, to add a string to a number, so the browser has to cast that number to a string, and compared it using both primitive strings and String object.

    The test runs here: http://jsperf.com/comparing-primitive-to-object-string-numbers

    December 5th, 2012 at 15:33

    1. Panagiotis Tsalaportas

      Thank you Felipe for your kind words!
      Your benchmark is very interesting; it seems that all browsers favour the string object, to a point, for operations. I’d suggest you also have a look at the Number Object. ;-)

      December 6th, 2012 at 08:17

  3. Daniel Lewis

    It might be interesting to also try extending String.prototype and seeing if there is much of a difference calling the custom method from an object vs a primitive.

    It would also be interesting to see if there is any difference in the speed of operations on ‘this’ used in a custom method called from an object vs a primitive.

    December 5th, 2012 at 18:13

    1. Panagiotis Tsalaportas

      So true Daniel. There are many other things that can be benchmarked related to this topic; there’s also the Number Object. :-)

      December 6th, 2012 at 08:31

  4. Dave Herman

    I don’t understand the point of the .valueOf() tests. I don’t see any reason to ever use that method on a primitive string — it’s a noop!

    Dave

    December 5th, 2012 at 23:32

    1. Karel Jára

      But the difference in speeds of [str1 + ” there”] and [str1.valueOf() + ” there”] is rather strange. It is actually a difference of at least two orders in operations per second…

      December 6th, 2012 at 03:23

    2. Panagiotis Tsalaportas

      That’s true Karel, thanks for noting this.
      Dave, of course you wouldn’t use that on a primitive string, nor would you read a string’s value through a for-in loop; it’s an article on mozhacks, weired stuff going on here… ^^

      In a sentence, the final result would be developers to realize how many ways there are during runtime to access the String.prototype properties/methods, and how these code variations can affect the overall benchmark cases. ;-)

      December 6th, 2012 at 08:22

  5. Alberto Arena

    Really interesting article, thanks for sharing.

    December 6th, 2012 at 01:59

    1. Panagiotis Tsalaportas

      Thank you Alberto!

      December 6th, 2012 at 08:23

  6. Vyacheslav Egorov

    Microbenchmarks like 1, 2, 6, 7, 8, 9, 11 can all be constant folded away. Which is unlikely to be a case in a real world application. Some of those operations are also already subject to the loop invariant code motion so you end up measuring cost of a single operation plus the cost of loop that jsperf generated around it.

    I suggest generating a list of strings and applying the same operation to them in a loop and timing that.

    December 6th, 2012 at 02:42

    1. Panagiotis Tsalaportas

      well, generating a list of strings will introduce another set of “runtime variables” that will too affect the total operation time. I think measuring a single operation is what we want in this article. See my to reply to Dave plz, for more info.

      December 6th, 2012 at 08:25

      1. Vyacheslav Egorov

        so I sat down and wrote down *why* simple microbenchmarks like that are not useful:

        http://mrale.ph/blog/2012/12/15/microbenchmarks-fairy-tale.html

        December 16th, 2012 at 10:41

        1. Panagiotis Tsalaportas

          Vyacheslav, let me first say that your follow-up article is pretty impressive, really nice to provide us with this info as it’s something I’ve been looking for to see in comments.

          I think the very idea of creating the jsperf website was to provide some more “insight” as to whether some JavaScript library functions that deal with the *DOM* are faster than others for a variety of reasons, ie the presence of native quering commands. Though, yet again, that’s also something to be questioned for obvious reasons (as also expressed by other developers). Here, on the contrary, the purpose is not to discriminate which kind of variable declaration will produce faster results and as such to urge developers to use the one over the other, quite the contrary. It is a showcase that will provide most developers with some examples in order to see that (1) there are some degrees of freedom in the JS engine implementations of the ECMAScript specification, hence not every aspect of the language is implemented in exactly the same way across all engines, and (2) that subtle code variations will eventually trigger different ways of interpretting some very similar pieces of code.

          Actually, your post may add up as an additional resource for interested devs. And as the article mentions above, there is a lot more in a browser than its JS engine. Thanks for your comment. :-)

          December 16th, 2012 at 14:54

          1. Vyacheslav Egorov

            I don’t dispute neither usefulness nor importance of jsperf.

            My only point is: developers should be aware about microbenchmarking pitfalls and avoid deriving incorrect conclusions from them. That’s it :-)

            December 16th, 2012 at 15:07

  7. Arun David

    Really a useful informative article.. Thnx

    December 6th, 2012 at 03:47

    1. Panagiotis Tsalaportas

      Thanks Arun!

      December 6th, 2012 at 08:26

  8. Tomas Corral

    This post is really cool because is one of the big reasons to tell our students why not to use new String to create strings.

    December 6th, 2012 at 06:18

    1. Panagiotis Tsalaportas

      Nice to see that!

      December 6th, 2012 at 08:26

  9. Dylan Schiemann

    Nice article… I would be curious to see how Opera and Internet Explorer fair in your tests. In the past, IE 6/7/8 behaved quite differently from everything else (no surprise there, but for something as fundamental as string manipulation, it was disappointing).

    We wrote a pair of posts on this topic in 2008 (which are quite a bit dated now and could definitely use an update, but they cover some similar points): http://www.sitepen.com/blog/2008/05/09/string-performance-an-analysis/ and http://www.sitepen.com/blog/2008/06/09/string-performance-getting-good-performance-from-internet-explorer/

    December 6th, 2012 at 06:29

    1. Panagiotis Tsalaportas

      Thanks! It would be interesting to see these articles updated Dylan. :)

      December 6th, 2012 at 08:28

    2. Panagiotis Tsalaportas

      Actually, it’s worth doing the benchmarks on modern browsers, as that’s where most JS engine optimizations take place.

      December 7th, 2012 at 14:24

  10. Blaise Kal

    @Dylan Schiemann

    You can see how Opera and IE perform by clicking on the “… on jsPerf” links.

    December 6th, 2012 at 07:03

  11. Mauricio Samy Silva

    Many thanks Panagiotis,
    This useful article has been translated into Brazilian-Portuguese.
    Translation is hosted at: http://www.maujor.com/blog/2012/12/06/objetos-string-da-javascript-e-performance/

    December 6th, 2012 at 12:28

    1. Robert Nyman – Editor

      That’s great, thank you very much for the translation!

      December 7th, 2012 at 02:31

    2. Panagiotis Tsalaportas

      Thank’s a lot for this!

      December 7th, 2012 at 14:22

  12. Caesar

    This is useful article to analysis String. The sample codes and performance comparing is easy to know. Thank you.

    December 6th, 2012 at 18:55

    1. Panagiotis Tsalaportas

      Thanks!

      December 7th, 2012 at 14:22

  13. Swader

    Excellent article, thank you! Reshared into Google Plus’ Web Performance community at gplus.to/webperf

    December 17th, 2012 at 00:44

    1. Panagiotis Tsalaportas

      Thanks Swader for the link!

      December 17th, 2012 at 05:32

Comments are closed for this article.