【问题标题】:std::map order of insertions and performancestd::map 插入顺序和性能
【发布时间】:2023-02-23 01:20:02
【问题描述】:

如果我想加载相对较大的成对文件 <string-key, int-value> 并使用 std::map 来存储数据。当我一个一个地加载每个条目并将其插入地图时,插入操作将花费 O(log N)。我想通过对文件中的条目进行排序来改进这一点,以确保当我将每个条目一个接一个地加载到文件中时,插入将只进行一次迭代。这可以通过在文件中提供正确的条目顺序来实现。问题是顺序是什么?假设与地图排序相同的顺序是正确的吗?我默认使用标准的字符串比较方法,就像 std::map 所做的那样。

【问题讨论】:

  • 如果您之前可以对条目进行排序并且键已经是唯一的,那么首先可能不需要 std::map 。你可以使用std::vector&lt; std::pair&lt;std::string,int&gt;&gt;
  • 不清楚你的意思到底是什么“......以确保当我一个一个地加载每个条目时,文件插入将只进行一次迭代。”
  • std::map 可能是一棵红黑树,但不能保证,任何插入顺序都是特定于标准库实现的
  • 如果您按顺序插入元素,您可以使用 insert/emplace 并提示在何处放置值(最后),那么您就可以保证恒定的复杂性。
  • 注意 insert 的重载有一个 pos 参数来提示元素应该去哪里。如果文件中的数据使用 end() 迭代器排序,则迭代器将正常工作。在 std::vector 中预排序很可能毫无意义。请测量一下以确保。

标签: c++ sorting stdmap


【解决方案1】:

是的,如果你在value_comp() order中有一系列的对,你可以使用the 4th overload

template< class InputIt >
map(InputIt first, InputIt last);

如果序列按value_comp()顺序排序,则需要花费线性时间。

【讨论】:

  • 从我的基准测试来看,这种过载性能很糟糕(对于 gcc 和 clang)。见其他答案。
【解决方案2】:

正如我在 cmets 中所写:

注意 insert 的重载有一个 pos 参数来提示元素应该去哪里。如果文件中的数据使用 end() 迭代器排序,则迭代器将正常工作。在 std::vector 中预排序很可能毫无意义。

不要假设任何编写基准测试并测量每个可能的实现。请注意,编写良好的 perforce 测试非常棘手,因为优化器可能比您更聪明。

所以这里有一些基准:

#include <sstream>
#include <map>
#include <iomanip>
#include <algorithm>
#include <numeric>
#include <random>

using TestData = std::vector<std::pair<std::string, int>>;

std::string makeStringFor(size_t x)
{
  std::ostringstream out;
  out << std::setfill('0') << std::setw(6) << x;
  return out.str();
}

TestData makeTestDataSorted(size_t n)
{
  TestData r;
  r.reserve(n);
  size_t i = 0;
  std::generate_n(std::back_inserter(r), n, [&i]() {
    return std::pair{makeStringFor(++i), i};
  });
  return r;
}

TestData makeTestDataShuffled(size_t n)
{
  auto data = makeTestDataSorted(n);
  std::random_device rd;
  std::mt19937 g(rd());

  std::shuffle(data.begin(), data.end(), g);
  return data;
}

static void CreateMapFormSortedDataInsert(benchmark::State& state) {
  auto n = state.range(0);
  auto data = makeTestDataSorted(n);
  for (auto _ : state) {
    benchmark::DoNotOptimize(data);
    std::map<std::string, int> m;
    
    for (auto& p : data) {
      m.insert(m.end(), p);
    }
    benchmark::DoNotOptimize(m);
  }
}
// Register the function as a benchmark
BENCHMARK(CreateMapFormSortedDataInsert)->RangeMultiplier(2)->Range(8, 8<<4);

static void CreateMapDirectlyFormSortedData(benchmark::State& state) {
  auto n = state.range(0);
  auto data = makeTestDataSorted(n);
  for (auto _ : state) {
    benchmark::DoNotOptimize(data);
    std::map<std::string, int> m{data.begin(), data.end()};
    benchmark::DoNotOptimize(m);
  }
}
// Register the function as a benchmark
BENCHMARK(CreateMapDirectlyFormSortedData)->RangeMultiplier(2)->Range(8, 8<<4);


static void CreateMapFormShuffledDataInsert(benchmark::State& state) {
  auto n = state.range(0);
  auto data = makeTestDataShuffled(n);
  for (auto _ : state) {
    benchmark::DoNotOptimize(data);
    std::map<std::string, int> m;
    
    for (auto& p : data) {
      m.insert(m.end(), p);
    }
    benchmark::DoNotOptimize(m);
  }
}
// Register the function as a benchmark
BENCHMARK(CreateMapFormShuffledDataInsert)->RangeMultiplier(2)->Range(8, 8<<4);

static void FirstSortVectorThenCreateMap(benchmark::State& state) {
  auto n = state.range(0);
  auto data = makeTestDataShuffled(n);
  for (auto _ : state) {
    benchmark::DoNotOptimize(data);
    auto sorted = data;
    std::sort(sorted.begin(), sorted.end());
    std::map<std::string, int> m;
    
    for (auto& p : sorted) {
      m.insert(m.end(), p);
    }
    benchmark::DoNotOptimize(m);
  }
}
// Register the function as a benchmark
BENCHMARK(FirstSortVectorThenCreateMap)->RangeMultiplier(2)->Range(8, 8<<4);

结果:

调整这些测试,使它们更精确地匹配您的用例。这非常重要,请参阅最后一段。现在,如果您输入的数据已排序 insertpos 参数看起来是最佳选择。

这也证明了std::vector中的数据先排序是没有意义的。

我也很惊讶直接从迭代器构建地图表现如此糟糕。 Caleth 在下面的评论中发现 std::pair 中的 constsurprising large impact here。将 const 添加到第一个类型构造函数后成为最快的选择。

【讨论】:

  • 这些图表中的 x 轴是什么?元素个数?它最多只能达到128。
  • 数据大小,注意每个测试的-&gt;RangeMultiplier(2)-&gt;Range(8, 8&lt;&lt;4);auto n = state.range(0);
  • 如果输入的值太大,站点将超时测试并且不会提供结果。
  • @MooingDuck 没有在 MSVC 上运行这些基准测试,并且为 gcc 和 clang 提供的链接证明该编译器不会忽略插入提示。
  • 看起来是因为您正在测试std::pair&lt;std::string, int&gt;,而不是std::pair&lt;const std::string, int&gt;。如果你解决了这个问题,迭代器构造函数会变得更快:quick-bench.com/q/4NGYB7i-toKw_j-GVaoV8-fUMhA
猜你喜欢
  • 2015-05-06
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 2019-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多