【问题标题】:Does std::move helps with objects on stack?std::move 对堆栈上的对象有帮助吗?
【发布时间】:2021-04-18 00:11:07
【问题描述】:

我今天刚看到一所大学在做这个优化:

即,改变这个:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;

std::string myfunction( const std::string& mystring, int myint )
{
    myglobalmap.insert( std::pair<std::string, int>( mystring, myint ) );
}
myfunction(somestring, 10)

收件人:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;

std::string myfunction( std::string mystring, int myint )
{
    myglobalmap[ std::move(mystring) ] = myint;
}
myfunction(somestring, 10)

他声称通过复制传递值 mystring 而不是将其作为常量引用传递会更快,因为移动操作只会对堆栈上的对象执行。但我不明白为什么这会有所帮助。我搜索了移动运算符,发现它不是move anything,它只是让我的表达式返回一个引用。

那么,如果这是真的,通过副本传递值不会比通过引用传递它并调用std::move 慢,或者在这种情况下调用std::move 对堆栈上的对象有帮助吗?如果我理解正确,std::move 应该只帮助处理堆上的对象。那么,用堆栈上的东西调用它应该没有帮助还是有帮助?

相关问题:

  1. What is std::move(), and when should it be used?
  2. Is it possible to std::move local stack variables?

【问题讨论】:

  • 通过这样的调用,字符串副本只是从函数内部移动到外部...myfunction(std::move(somestring), 10); 会避免复制...但会变异somestring
  • 字符串内部使用动态内存。

标签: c++ c++11 move


【解决方案1】:

我发现它并没有移动任何东西,它只是让我的表达式返回一个引用。

这是正确的。至关重要的是,函数的结果是一个 xvalue。

那么,如果这是真的,通过复制传递值不会比通过引用传递并调用 std::move 或调用 std::move 慢

std::string 的情况下(假设包含的字符串不是很小),复制和移动比通过引用和移动的间接速度慢。


在您的第一个示例中,您通过引用间接并始终复制。

在第二个示例中,仅当函数参数是左值时才进行复制。当参数是右值时,第二个比第一个快(只要存储的字符串足够长以使差异显着)。


两个版本都有未定义的行为,因为函数被声明为返回非 void,但不返回值。

【讨论】:

  • 如果这样的密钥不存在,稍后可能会更快。如果它很可能会更慢。
  • @Slava 您是如何得出第二个结论的?复制字符串具有线性时间复杂度。
  • 1) " ... 复制和移动比通过引用和移动间接进行的速度要慢" -> OK 同意。 2)“...当参数是右值时,第二个比第一个快”-> OK 同意。当参数是左值时,我仍然没有找到 Q 的答案,我通过引用进行间接然后复制它。
  • 同意你的意见。但是我对提到的两个函数进行了测试,并将左值传递给它们。在我的测试中,第二个比第一个快(70% 用于调试,90% 发布)。我仍然无法解释这一点。 (Windows 10、VS2015)
  • @Gupta 基准测试非常困难,单个百分比数字并不能说明任何意义。这种差异可能会受到环境中完全偶然的变化的影响。也就是说,当地图中确实存在密钥时,第一个版本比第二个版本移动更多。您的基准主要衡量已经存在的密钥的情况。这也有点不公平,因为第一个函数被测试一次然后地图是空的,尽管这应该有一个稀释的效果。
【解决方案2】:

首先,我想说这会改变myfunction 的行为。如果键已存在于映射中,则第一个版本不会插入整数。第二个版本用新值替换整数。

也就是说,如果传递的字符串还没有在映射中,因此它会制作一个副本,这可能会稍微更有效率。如果 myfunction 传递一个临时值,编译器可以移动构造(甚至优化移出),然后将其移动到映射中。虽然std::move 不会移动任何东西,但使用它会导致map::operator[] 使用右值引用重载,这反过来可以调用std::string 上的移动构造函数。

但是,如果密钥已经存在,则可能会导致创建一个不需要的额外副本。

【讨论】:

  • 你能直接回答Q的情况吗,其中1)传递的字符串不是临时的,2)key总是不存在于map中?
  • " 如果传递的字符串不在映射中,因此它会进行复制,这可能会更有效。" 请详细说明。对于不经意的审阅者来说,该副本已经由字符串的传值副本支付。
【解决方案3】:

我错过了问题中的一个重要点,在myglobalmap.insert( 和新的myglobalmap[ 之前有一个锁。

也就是说,变化是这样的:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;
std::mutex g_pages_mutex;

std::string myfunction( const std::string& mystring, int myint )
{
    std::lock_guard<std::mutex> guard(g_pages_mutex);
    myglobalmap.insert( std::pair<std::string, int>( mystring, myint ) );
    return "";
}
myfunction(somestring, 10)

收件人:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;
std::mutex g_pages_mutex;

std::string myfunction( std::string mystring, int myint )
{
    std::lock_guard<std::mutex> guard(g_pages_mutex);
    myglobalmap[ std::move(mystring) ] = myint;
    return "";
}
myfunction(somestring, 10)

因此,作为@eerorika 提供的答案的扩展,执行的优化是在获取锁之前调用复制构造函数。这意味着第二个版本应该更快,因为复制是在不持有锁的情况下执行的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-04
    • 2018-02-25
    • 1970-01-01
    • 2015-11-21
    • 1970-01-01
    • 2020-12-26
    • 2016-02-04
    • 2011-05-04
    相关资源
    最近更新 更多