Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a string by value, reference and rvalue

I'm just comparing the performance of passing a string to a function. The benchmark results are interesting.

Here's my code:

void add(std::string msg)
{
    msg += "world";
}

void addRvalue(std::string&& msg)
{
    msg += "world";
}

void addRef(std::string& msg)
{
    msg += "world";
}

void StringCreation() {
    add(std::string("hello "));
}

void StringCopy() {
    std::string msg("hello ");
    add(msg);
}

void StringMove() {
    std::string msg("hello ");
    add(std::move(msg));
}

void StringRvalue() {
    std::string msg("hello ");
    addRvalue(std::move(msg));
}

void StringReference() {
    std::string msg("hello ");
    addRef(msg);
}

StringCreation(), StringRvalue() and StringReference() are equivalent. I'm surprised StringMove() is the least performant - worse than pass by value which involves a copy.

Am I right in thinking that calling StringMove() involves one move constructor followed by a copy constructor when it calls add()? It doesn't just involve one move constructor? I thought move construction was cheap for a string.

Update

I increased the length of the string passed to add() and that did make a difference. Now StringMove() is only 1.1 times slower than StringCreation and StringReference. StringCopy is now the worst, which is what I expected.

Here are the new benchmark results.

So StringMove doesn't involve copying after all - only for small strings.

like image 298
jignatius Avatar asked Aug 30 '19 10:08

jignatius


People also ask

Can you pass a string by reference?

Pass String by Reference in C++ The C++ reference is a name for a variable that already exists. A reference to a variable can't be altered to refer to the other variable once initialized. Pointers or references can be passed as parameters to functions in C++.

Is string passed by reference in C++?

strings are passed by reference. The built in string type is a value type. Variables (also function/method arguments from now on) and struct fields of type string are passed/copied by value.

Are strings passed by value in C?

If the function needs to modify a dynamically allocated (i.e. heap-allocated) string buffer from the caller, you must pass in a pointer to a pointer. In C, function arguments are passed by value. This means that to modify a variable from within a function, you need a pointer to the variable.

Can a function return an lvalue?

Why is it not possible to set a function returning an address while it is possible to set a function that returns a reference. Short answer: You return a pointer by-value and anything returned by-value is (by definition) not an lvalue.


1 Answers

Let's analyze your code and suppose long strings (without applied SSO):

void add(std::string msg) {
   msg += "world";
}

void StringCreation() {
   add(std::string("hello "));
}

Here, a converting constructor (ConvC) from the string literal is called first to initialize the temporary std::string("hello "). This temporary (an rvalue) is then used to initialize the parameter msg by the move constructor (MC). However, the latter is very likely optimized away by copy elision. Finally, the operator += is called. Bottom line: 1x ConvC and 1x +=.

void StringCopy() {
   std::string msg("hello ");
   add(msg);
}

Here, the parameter msg is copy-initialized (by copy constructor - CC) by the lvalue argument msg. Bottom line: 1x ConvC, 1x CC, and 1x +=. In case of long strings, this is the slowest version, since copy involves dynamic memory allocations (the only case).

void StringMove() {
   std::string msg("hello ");
   add(std::move(msg));
}

Why is this slower than StringCreation? Simply because there is an additional MC involved that initializes the parameter msg. It cannot be elided, since the object msg still exist after the call of add. Just it is moved-from. Bottom line: 1x ConvC, 1x MC, 1x +=.

void addRef(std::string& msg) {
   msg += "world";
}

void StringReference() {
   std::string msg("hello ");
   addRef(msg);
}

Here, the operator += is applied to the referenced object, so there is no reason for any copy/move. Bottom line: 1x ConvC, 1x +=. Same time as for StringCreation.

void addRvalue(std::string&& msg) {
   msg += "world";
}

void StringRvalue() {
   std::string msg("hello ");
   addRvalue(std::move(msg));
}

With Clang, the time is same as for StringReference. With GCC, the time is same as for StringMove. In fact, I don't have an explanation for this behavior for now. (It seems to me that GCC is creating some additional temporary initialized by MC. However, I don't know why.)

like image 158
Daniel Langr Avatar answered Oct 02 '22 04:10

Daniel Langr