通过值,引用和右值传递字符串

时间:2019-08-30 10:58:08

标签: c++ c++11 stdstring

我只是比较将字符串传递给函数的性能。 benchmark results很有趣。

这是我的代码:

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()和StringReference()是等效的。我很惊讶StringMove()性能最差-比涉及复制的值传递差。

我是否正确地认为调用StringMove()涉及一个move构造函数,然后在调用add()时涉及一个复制构造函数?它不仅仅涉及一举一动的构造函数吗?我认为移动构造对于字符串来说很便宜。

更新

我增加了传递给add()的字符串的长度,这确实有所作为。现在,StringMove()仅比StringCreation和StringReference慢1.1倍。现在,StringCopy是最糟糕的,这正是我所期望的。

这是新的benchmark results

因此,StringMove根本不涉及复制-仅适用于小字符串。

2 个答案:

答案 0 :(得分:5)

让我们分析您的代码并假设使用长字符串(未应用SSO):

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

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

在这里,首先调用字符串文字中的转换构造函数 ConvC ),以初始化临时std::string("hello ")。然后,该临时值(右值)由移动构造函数 MC )用于初始化参数msg。但是,后者很可能通过 copy Elision 进行了优化。最后,调用运算符+=。底线: 1x ConvC和1x +=

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

在这里,参数msg lvalue 参数{{通过复制构造函数- CC )复制初始化。 1}}。底线: 1x ConvC,1x CC和1x msg 。对于长字符串,这是最慢的版本,因为复制涉及动态内存分配(唯一的情况)。

+=

为什么这比void StringMove() { std::string msg("hello "); add(std::move(msg)); } 慢?仅仅是因为有一个附加的MC可以初始化参数StringCreation。由于对象msg在调用msg之后仍然存在,因此不能忽略它。只是它是从。底线: 1x ConvC,1x MC,1x add

+=

在这里,运算符void addRef(std::string& msg) { msg += "world"; } void StringReference() { std::string msg("hello "); addRef(msg); } 应用于引用的对象,因此没有理由进行任何复制/移动。底线: 1x ConvC,1x += 。与+=的时间相同。

StringCreation

使用Clang时,时间与void addRvalue(std::string&& msg) { msg += "world"; } void StringRvalue() { std::string msg("hello "); addRvalue(std::move(msg)); } 相同。使用GCC,时间与StringReference相同。实际上,我目前没有这种行为的解释。 (在我看来,GCC正在创建由 MC 初始化的其他一些临时文件。但是,我不知道为什么。)

答案 1 :(得分:0)

在此示例中,没有任何被“基准化”的功能实际上在起作用。也就是说,它们都没有实际返回计算值,然后将其用于其他地方。

因此,任何(半)像样的编译器都可能会决定完全忽略它们!

为了建立有效的基准,每次调用的结果字符串必须必须用于某些内容,甚至是简单输出到文件/控制台。

尝试以下代码以查看发生了什么(不是):

#include<iostream>
#include<string>

using namespace std;

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

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

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

int main()
{
    std::string msg("Initial string!");
    cout << msg << endl;
    add(msg);
    cout << msg << endl; // msg will be the same as before!
    addRef(msg);
    cout << msg << endl; // msg will be extended!
    addRvalue(std::move(msg));
    cout << msg << endl; // msg will again be extended
    add(std::move(msg)); 
    cout << msg << endl; // msg will be completely emptied!
    return 0;
}
相关问题