4
\$\begingroup\$

For some time, JavaScript string concatenation could be optimised by join()ing an array of strings, instead of +ing them together.

Recently, browsers have been optimised to handle strings better, and +ing is back in favour.

The various posts I've read on the subject have cited many jsperf.com tests, which are invariably regular, and nothing much like the dynamic irregularity typically encountered in most web apps that need to concatenate on the fly.

So, I built a new test, the idea of which is to subject each method of concatenation to a relatively equal degree of randomness.

The results show a similar performance difference as other tests of arr.join("") vs. str + str.

Question: Is this jsperf.com test code good jsperf.com test code; is there method in my madness, or am I just mad?

Familiararity with jsperf.com testing is assumed.


"Where a script is concatenating arbitrary numbers of strings dynamically and often, is it 'better' to define a function to handle it, arr.join() inline, or perform simple inline concatenations?"

Preparation code:

Here I aim simply to create a large string from which random substrings can be extracted and stored to global vars for concatenation during the test runs. Also create a couple of functions, one for programmatic concatenation of an arbitrary number of strings using the arr.join("") method, and another for the random selection of the global substrings.

var base = "so many ways to skin a cat",
    c = 0, bl, hbl,
    rand = function( s, e ) {
        return Math.floor( Math.random() * ( e - s ) ) + s;
    },
    concatenateThese = function() {
        return [].slice.call( arguments ).join( "" );
    };
while ( ++c < 10 ) {
    base += base;
}
bl = base.length;
hbl = Math.floor( bl / 2 );
c = 0;
while ( ++c < 100 ) {
    window[ "randStr" + c ] = base.substr( rand( 0, hbl ), rand( hbl, bl ) );
}

My intention is that all the following test snippets are subject to a realistically equal amount of computational randomness where e.g. gluing an identical string to itself for 10000 iterations, can't be itself optimised. Each value is unknowable until runtime.

Test Snippet 1:

This should concatenate 7 randomly selected global strings from those created by the preparation code, by use of the + operator.

var rslt = window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ] +
    window[ "randStr" + rand( 0, 100 ) ];

Test Snippet 2:

This should concatenate 7 randomly selected global strings from those created by the preparation code, by use of the join() method.

var rslt = [ window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ] ].join( "" );

Test Snippet 3:

This should concatenate 7 randomly selected global strings from those created by the preparation code, by use of the join() method via a handler function created by the preparation code.

var rslt = concatenateThese( window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ],
    window[ "randStr" + rand( 0, 100 ) ] );
\$\endgroup\$
  • 1
    \$\begingroup\$ In general, you should create one set of random strings and then have all three tests use the same strings. That way you don't have any random variability between the different tests. \$\endgroup\$ – jfriend00 Feb 8 '16 at 3:26
  • \$\begingroup\$ @jfriend00 - my intention is to make it impossible for the browser to optimise the concatenation by removing the knowledge of what strings are to be concatenated before runtime. If all the snippets were using "the same strings", the browser (especially Chrome (V8) I think) can optimise for this known quantity and the reality of dynamic string concatenation on the fly is obscured. \$\endgroup\$ – Fred Gandt Feb 8 '16 at 3:34
  • 1
    \$\begingroup\$ I know that's your intention, but that gives you a situation where you're not using the same strings in different tests and I don't think that's a valid comparison. If you really want to see if it matters, you can run the test, see which is faster, reorder the tests and see which is faster, reorder the tests and see which is faster since there's no way it could be optimized for the first test that runs. I think a fair test is more important than the likelihood of some browser optimization over a previously used string. \$\endgroup\$ – jfriend00 Feb 8 '16 at 3:40
  • \$\begingroup\$ @jfriend00 - it is my understanding that the method and number of concatenations has a far greater impact on performance than the length of the strings where those strings are relatively small and similar. As you understand the question, if you feel like providing an answer, please do so. \$\endgroup\$ – Fred Gandt Feb 8 '16 at 4:38
1
\$\begingroup\$

Is this jsperf.com test code good jsperf.com test code; is there method in my madness, or am I just mad?

Probably. This is a micro-optimization whose performance is really negligible in recent browsers. The only browsers I remember choking when it comes to strings were IE8 and older.

With that said, out of all your methods, concatenation with + is an obvious winner, all because it operates solely on strings. The other methods use an array as glue.

Test #2 is at a disadvantage as it has to wrap the strings into an array before they get concatenated by join. Test #3 is in even more disadvantage as you are creating an array twice, first on [] and second on slice.

And even with that, in terms of readability, all three look horrible in the presence of template strings which was designed to improve concatenation and interpolation in strings. It should be present on Firefox, Chrome and Edge as well as transpiler setups. Babel uses the + method when transpiling template strings, so there must be a reason for doing so.


So I have skimmed over the actual test code, and focused more on the approaches. By the way, I believe jsperf is just a front and persistent service that uses Benchmark.js under the hood. So if you're doing any actual testing, best use the library directly and locally.

var base = "so many ways to skin a cat",
  c = 0, bl, hbl,
  rand = function( s, e ) {
    return Math.floor( Math.random() * ( e - s ) ) + s;
  },
  concatenateThese = function() {
    return [].slice.call( arguments ).join( "" );
  };

A lot of vaguely named variables. What's c, bl, hbl, s, e and everything else? Tests are made to verify code. If I can't read the test, how do I know it works? How do I know what it's supposed to do?

rand appears to be a ranged random number generator. Name it as such. concatenateThese... concatenate what exactly? Sure, there's arguments, but the thing about code is that if I cannot know what the code does without having to dig up the source or pull up a debugger, then that's terrible code.

var base = "so many ways to skin a cat",

/* ton of code between */

while ( ++c < 10 ) {
    base += base;
}

Why are these two pieces of code separate? If I were reading code and the other part was 100 lines or more apart, I would wonder how base was 10 times the size. I'd have to (gasp) dig up the source... again.

By the way, another way of multiplying a string is

Array(10).join('the sentence you want multiplied');

You happen to use window. That's... bad. Nobody wants to pollute the global namespace. Suggesting you store those random strings in an array or object, not on window. While jsperf tests are isolated from each other, your prep code and tests aren't. There's a chance you might introduce something in either code that can infect the other, causing erroneous test results. Sure, you namespaced with randStr, but that doesn't guarantee that you won't mess it up in the long run. Writing tests is tedious, refactoring tests is a nightmare. Best done right always.

\$\endgroup\$
  • \$\begingroup\$ So regardless of the code being tested (which is irrelevant), you feel that this is good test code? \$\endgroup\$ – Fred Gandt Feb 8 '16 at 4:35
  • \$\begingroup\$ By "irrelevant" I mean - you're evaluating the results of the test, which are relevant except if those results do not illuminate as intended. \$\endgroup\$ – Fred Gandt Feb 8 '16 at 6:08
  • \$\begingroup\$ @FredGandt updated. Also, best if you shorten up your post and put more emphasis on the testing approach rather than expound a lot on string concatenation performance (which is like 1/3 of the post and happens on the intro). Even you first comment to jfriend00 was about performance even. \$\endgroup\$ – Joseph Feb 8 '16 at 13:21
  • 1
    \$\begingroup\$ Describing why code exists is essential for review IMO. My first comment in response to jfriend00 explains further the intention of the code. The title was edited by Quill, and IMO is now less illustrative. \$\endgroup\$ – Fred Gandt Feb 8 '16 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.