为什么.push_back(x)比.push_back(std::move(x))快

我有一个很大的 .txt 文件,需要加载并存储在向量中。该文件大小约为 5MB,500 000 行,每行约 10-20 个字符,以 'n' 分隔。我正在使用以下示例代码对读取整个文件所需的时间进行一些基准测试:

#include<iostream>
#include<vector>
#include<fstream>

int main()
{
    std::fstream input("words.txt");
    std::vector<std::string> vector;
    std::string line;

    while(input >> line){
        vector.push_back(line);
    }
}

我很好奇将字符串作为右值引用传递是否会更快,但它会慢大约 10 毫秒。

#include<iostream>
#include<vector>
#include<fstream>

int main()
{
    std::fstream input("words.txt");
    std::vector<std::string> vector;
    std::string line;

    while(input >> line){
        vector.push_back(std::move(line));
    }
}

第一个代码示例的平均加载时间约为 58 毫秒,第二个代码示例的平均加载时间为 68-70 毫秒。我在想移动总是更快或等于复制,这就是为什么这对我来说似乎不正确。

有谁知道发生这种情况的原因?基准测试是使用以下方法完成的:

perf stats -r 100 ./a.out

在 Arch Linux 上,代码已使用 GCC 10.2、C++17 std 编译。

此外,如果有人知道这样做的更佳方法,我们将不胜感激。

回答

如果您调用,g++ -E您可以查看相关代码:

复制构造:

  basic_string(const basic_string& __str)
    : _M_dataplus(_M_local_data(),
                  _Alloc_traits::_S_select_on_copy(__str._M_get_allocator()))
  {
      _M_construct(__str._M_data(), __str._M_data() + __str.length());
  }

移动建设:

# 552 "/usr/local/include/c++/10.2.0/bits/basic_string.h" 3
basic_string(basic_string&& __str) noexcept
  : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator()))
{
    if (__str._M_is_local())
    {
        traits_type::copy(_M_local_buf, __str._M_local_buf,
                          _S_local_capacity + 1);
    }
    else
    {
        _M_data(__str._M_data());
        _M_capacity(__str._M_allocated_capacity);
    }
    _M_length(__str.length());
    __str._M_data(__str._M_local_data());
    __str._M_set_length(0);
}

值得注意的是,(为了支持短字符串优化)移动构造函数需要查看._M_is_local()是否要复制或移动(因此有一个分支要预测),并清除移动的字符串/将其长度设置为0. 额外的工作 = 额外的时间。


@Manuel 发表了一个有趣的评论:

当您移动行时,它会变空,因此下一次迭代需要分配空间。std::string 有一个小的缓冲区作为优化(大多数实现,如果不是全部的话)并且副本只是复制字符,没有内存分配。这可能就是不同之处。

这并不像所说的那样加起来,但这里有细微的差别。对于输入空格分隔的单词足够长以需要动态分配,:

a) move version 可能line在最后一个字之后清除了的动态缓冲区,因此需要重新分配;如果输入足够长,则可能需要重新分配一次或多次以增加容量

b) 复制版本可能有足够大的缓冲区(它的容量将根据需要增加,有效地成为所见单词的高水位线),但是在内部构建复制时将需要动态分配push_back。不过,该分配的确切大小是预先知道的——不需要调整大小来增加容量。

这确实表明当输入字长有很多变化时,复制可能会更快。


此外,如果有人知道这样做的更佳方法,我们将不胜感激。

如果您真的关心性能,我建议您对映射文件的内存进行基准测试,并在其中创建一个vectorof string_views:这可能会快得多。


以上是为什么.push_back(x)比.push_back(std::move(x))快的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>