Js Strings "+" VS Concat Method

JS strings + vs concat method

MDN has the following to say about string.concat():

It is strongly recommended to use the string concatenation operators
(+, +=) instead of this method for perfomance reasons

Also see the link by @Bergi.

What is the difference of + operator and concat() method in JavaScript

There are pretty the same but + is more clear and for sure faster.

Look at this performance test and also see this

It is strongly recommended that assignment operators (+, +=) are used instead of the concat() method.

When would you use .concat() in javascript

Sometimes its best to consult the documentation: Array.concat, and String.concat.

Simply, Array.concat() is used to create a new array equivalent to the flat merging of all passed in objects (arrays or otherwise). String.concat() is used to create a new string, which is equivalent to the merging of all passed in strings.

However, as MDN hints at, String.concat() should not be used as the assignment +, += operators are much faster. Why then would you use String.concat()? You wouldn't. Why have it then? It's part of the spec: See Page 111 - 112 (Section: 15.5.4.6).

So on to the question of Why is String.Concat so slow?. I did some digging through Chrome's V8 Engine. To start with, behind the scenes, this is what a call to String.prototype.concat is doing:

// ECMA-262, section 15.5.4.6
// https://github.com/v8/v8/blob/master/src/string.js#L64
function StringConcat(other /* and more */) { // length == 1
CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");

var len = %_ArgumentsLength();
var this_as_string = TO_STRING_INLINE(this);
if (len === 1) {
return this_as_string + other;
}
var parts = new InternalArray(len + 1);
parts[0] = this_as_string;
for (var i = 0; i < len; i++) {
var part = %_Arguments(i);
parts[i + 1] = TO_STRING_INLINE(part);
}
return %StringBuilderConcat(parts, len + 1, "");
}

As you can see all of the real work happens in StringBuilderConcat, which then calls a StringBuilderConcatHelper which then finally calls String::WriteToFlat to build a string. These are each extremely long functions and I've cut most of it out for brevity. But if you'd like to look for your self have a look in github:

StringBuilderConcat

// https://github.com/v8/v8/blob/master/src/runtime.cc#L7163
RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
// ...
StringBuilderConcatHelper(*special,
answer->GetChars(),
FixedArray::cast(array->elements()),
array_length);
// ...
}

StringBuilderConcatHelper

template <typename sinkchar>
static inline void StringBuilderConcatHelper(String* special,
sinkchar* sink,
FixedArray* fixed_array,
int array_length) {

// ...
String::WriteToFlat(string, sink + position, 0, element_length);
// ...
}

String::WriteToFlat

// https://github.com/v8/v8/blob/master/src/objects.cc#L8373
template <typename sinkchar>
void String::WriteToFlat(String* src,
sinkchar* sink,
int f,
int t) {
String* source = src;
int from = f;
int to = t;
while (true) {
// ...
// Do a whole bunch of work to flatten the string
// ...
}
}
}

Now what's different about the assignment pathway? Lets start with the JavaScript addition function:

// ECMA-262, section 11.6.1, page 50.
// https://github.com/v8/v8/blob/master/src/runtime.js#L146
function ADD(x) {
// Fast case: Check for number operands and do the addition.
if (IS_NUMBER(this) && IS_NUMBER(x)) return %NumberAdd(this, x);
if (IS_STRING(this) && IS_STRING(x)) return %_StringAdd(this, x);

// Default implementation.
var a = %ToPrimitive(this, NO_HINT);
var b = %ToPrimitive(x, NO_HINT);

if (IS_STRING(a)) {
return %_StringAdd(a, %ToString(b));
} else if (IS_STRING(b)) {
return %_StringAdd(%NonStringToString(a), b);
} else {
return %NumberAdd(%ToNumber(a), %ToNumber(b));
}
}

First thing to note, there's no loops and its quite a bit shorter compared to StringConcat up above. But most of the work we're interested in happens in the %_StringAdd function:

// https://github.com/v8/v8/blob/master/src/runtime.cc#L7056
RUNTIME_FUNCTION(Runtime_StringAdd) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(String, str1, 0);
CONVERT_ARG_HANDLE_CHECKED(String, str2, 1);
isolate->counters()->string_add_runtime()->Increment();
Handle<String> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, isolate->factory()->NewConsString(str1, str2));
return *result;
}

This is pretty simple actually, some counters and a call to something called NewConsString with the left and right operands. NewConsString is also pretty simple:

// https://github.com/v8/v8/blob/master/src/ast-value-factory.cc#L260
const AstConsString* AstValueFactory::NewConsString(
const AstString* left, const AstString* right) {
// This Vector will be valid as long as the Collector is alive (meaning that
// the AstRawString will not be moved).
AstConsString* new_string = new (zone_) AstConsString(left, right);
strings_.Add(new_string);
if (isolate_) {
new_string->Internalize(isolate_);
}
return new_string;
}

So this just returns a new AstConsString, what's that:

// https://github.com/v8/v8/blob/master/src/ast-value-factory.h#L117
class AstConsString : public AstString {
public:
AstConsString(const AstString* left, const AstString* right)
: left_(left),
right_(right) {}

virtual int length() const OVERRIDE {
return left_->length() + right_->length();
}

virtual void Internalize(Isolate* isolate) OVERRIDE;

private:
friend class AstValueFactory;

const AstString* left_;
const AstString* right_;
};

Well this doesn't look like a string at all. Its actually an 'Abstract Syntax Tree', this structure forms a 'Rope' which is efficient for modifying strings. It turns out most of the other browsers now use this type or rope structure when doing string addition.

The take away from this, is that the addition pathway uses a more efficient data structure, where as StringConcat does significantly more work with a different data structure.

Which way is better in Javascript, string concatenation or with array?

This answers your question: High-performance String Concatenation in JavaScript

ES6 template literals vs. concatenated strings

If you are using template literals only with placeholders (e.g. `Hello ${person.name}`) like in the question's example, then the result is the same as just concatenating strings. Subjectively it looks better and is easier to read, especially for multi-line strings or strings containing both ' and " since you don't have to escape those characters any more.

Readability is a great feature, but the most interesting thing about templates are Tagged template literals:

let person = {name: 'John Smith'}; 
let tag = (strArr, name) => strArr[0] + name.toUpperCase() + strArr[1];
tag `My name is ${person.name}!` // Output: My name is JOHN SMITH!

In the third line of this example, a function named tag is called. The content of the template string is split into multiple variables, that you can access in the arguments of the tag function: literal sections (in this example the value of strArr[0] is My name is and the value of strArr[1] is !) and substitutions (John Smith). The template literal will be evaluated to whatever the tag function returns.

The ECMAScript wiki lists some possible use cases, like automatically escaping or encoding input, or localization. You could create a tag function named msg that looks up the literal parts like My name is and substitutes them with translations into the current locale's language, for example into German:

console.log(msg`My name is ${person.name}.`) // Output: Mein Name ist John Smith.

The value returned by the tag function doesn't even have to be a string. You could create a tag function named $ which evaluates the string and uses it as a query selector to return a collection of DOM nodes, like in this example:

$`a.${className}[href=~'//${domain}/']`

String concatenation: concat() vs + operator

No, not quite.

Firstly, there's a slight difference in semantics. If a is null, then a.concat(b) throws a NullPointerException but a+=b will treat the original value of a as if it were null. Furthermore, the concat() method only accepts String values while the + operator will silently convert the argument to a String (using the toString() method for objects). So the concat() method is more strict in what it accepts.

To look under the hood, write a simple class with a += b;

public class Concat {
String cat(String a, String b) {
a += b;
return a;
}
}

Now disassemble with javap -c (included in the Sun JDK). You should see a listing including:

java.lang.String cat(java.lang.String, java.lang.String);
Code:
0: new #2; //class java/lang/StringBuilder
3: dup
4: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_2
12: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/ String;
18: astore_1
19: aload_1
20: areturn

So, a += b is the equivalent of

a = new StringBuilder()
.append(a)
.append(b)
.toString();

The concat method should be faster. However, with more strings the StringBuilder method wins, at least in terms of performance.

The source code of String and StringBuilder (and its package-private base class) is available in src.zip of the Sun JDK. You can see that you are building up a char array (resizing as necessary) and then throwing it away when you create the final String. In practice memory allocation is surprisingly fast.

Update: As Pawel Adamski notes, performance has changed in more recent HotSpot. javac still produces exactly the same code, but the bytecode compiler cheats. Simple testing entirely fails because the entire body of code is thrown away. Summing System.identityHashCode (not String.hashCode) shows the StringBuffer code has a slight advantage. Subject to change when the next update is released, or if you use a different JVM. From @lukaseder, a list of HotSpot JVM intrinsics.

Array Join vs String Concat

String concatenation is faster in ECMAScript. Here's a benchmark I created to show you:

http://jsben.ch/#/OJ3vo

Most efficient way to concatenate strings in JavaScript?

Seems based on benchmarks at JSPerf that using += is the fastest method, though not necessarily in every browser.

For building strings in the DOM, it seems to be better to concatenate the string first and then add to the DOM, rather then iteratively add it to the dom. You should benchmark your own case though.

(Thanks @zAlbee for correction)



Related Topics



Leave a reply



Submit