【问题标题】:Loading an STL set with pre-sorted data, C++加载带有预排序数据的 STL 集,C++
【发布时间】:2011-03-23 20:30:46
【问题描述】:

我在 Visual Studio 2010 中使用 C++。我有一个 STL 集,当我的程序关闭时我将其保存到文件中。下次程序启动时,我将(排序的)数据加载回一个集合中。我正在尝试优化加载过程,但遇到了麻烦。我怀疑问题出在频繁的重新平衡上,我正在寻找一种方法来避免这种情况。

首先,我没有优化,使用“set->insert (const value_type& x)”

时间:~5.5分钟

然后我尝试使用 insert() 的版本,您在其中传递了 insert() 位置的提示:

iterator insert ( iterator position, const value_type& x );

大致上,我是这样做的:

set<int> My_Set;
set<int>::iterator It;
It = My_Set.insert (0);
for (int I=1; I<1000; I++) {
   It = My_Set.insert (It, I);  //Remember the previous insertion's iterator
   }

时间: ~5.4 分钟

几乎没有任何改进!我认为问题不在于从文件读取的开销——注释掉 insert() 将时间减少到 2 秒。我认为问题不在于复制我的对象的开销——它是一个带有 int 和 char 的普通旧数据对象。

我唯一能想到的就是这个系列在不断地重新平衡。

1.) 你同意我的猜测吗?

2.) 有没有办法在我加载集合时“暂停”重新平衡,然后在最后重新平衡一次? (或者……这会有帮助吗?)

3.) 有没有更聪明的方法来加载排序的数据,而不是简单地从最低移动到最高?也许交替我的插入,这样它就不必经常平衡? (例如:插入 1、1000、2、999、3、998、...)

【问题讨论】:

  • 这是一个 DEBUG 版本吗?时间看起来像一个。
  • 这是一个调试版本。但我们正在处理大量数据,所以约 5.5 分钟并不让我感到惊讶。
  • @fazo:我做到了。那是我提到的约 5.5 分钟时间。
  • 为调试构建计时是没有意义的。
  • @user673772:如果从文件中读取需要 2 秒,那么插入一个集合需要 5 分钟,这听起来令人惊讶。在进行任何类型的性能测量之前启用优化。

标签: c++ stl set sorted


【解决方案1】:

我们在谈论多少个元素?

我用 10.000.000 个整数(在向量中准备)做了一个简短的测试,并以三种不同的方式将它们插入到集合中。

准备输入:

  std::vector<int> input;
  for(int i = 0; i < 10*1000*1000; ++i) {
     input.push_back(i);
  }


使用 insert 逐项插入设置:

发布:2.4 秒/调试:110.8 秒

  std::set<int> mySet;
  std::for_each(input.cbegin(), input.cend(), [&mySet] (int value) {
     mySet.insert(value);
  });


insert(itBegin, itEnd)插入集合:

发布:0.9 秒/调试:47.5 秒

  std::set<int> mySet;
  mySet.insert(input.cbegin(), input.cend());

  // this is also possible - same execution time:
  std::set<int> mySet(input.cbegin(), input.cend());

所以插入可以大大加快,但即使是慢的方式也应该远离几分钟。


编辑:

我同时使用调试模式进行了测试 - 哇 - 我知道调试成本性能,但它比我想象的要多。有 50.000.000 个元素在调试模式下存在错误分配,因此我将帖子更新为 10.000.000 个元素并显示发布和调试构建的时间。

您可以在这里看到巨大的差异 - 使用更快的解决方案可以看到 50 倍。

此外,快速解决方案 (insert(itBegin, itEnd)) 似乎与元素的数量呈线性关系(带有预排序的数据!)。以前的测试有五倍多的元素,插入时间从 4,6 减少到 0,9 - 大约是五倍。

【讨论】:

  • 谢谢,明天我必须在发布模式下试试。 (我正在等待项目中的其他人修复仅在发布模式下出现的编译器错误。)在调试模式下,我得到以下时间:1.) Set.insert (Val) -- 334sec 2 .) Prev_Iter=Set.insert (Prev_Iter, Val) -- 339sec 3.) Set.insert(Set.end(), Val) -- 329sec 4.) push_back() 将所有内容放入向量,然后 Set.insert (Vect .begin(), Vect.end()) -- 347sec 该数据与您的数据非常不同,这没有任何意义——一定是发生了与调试模式有关的事情。
【解决方案2】:

你尝试过范围构造函数吗?

#include <set>
#include <fstream>
#include <algorithm>
#include <iterator>

int main()
{
    std::ifstream  file("Plop");

    std::set<int>   myset;

    std::copy(std::istream_iterator<int>(file),
              std::istream_iterator<int>(),
              std::inserter(myset, myset.end()));
}

用 [0 -> 10,000,000) 个项目(按文件排序)尝试了 4 种技术:

void t1(std::set<int>& data, std::istream& file)
{
    int x;
    while(file >> x)    {data.insert(x); }
}

void t2(std::set<int>& data, std::istream& file)
{
    int x;
    while(file >> x)    {data.insert(data.end(), x);}
}

void t3(std::set<int>& data, std::istream& file)
{
    std::set<int>::iterator it = data.begin();
    int x;
    while(file >> x)    {it = data.insert(it, x);}
}

void t4(std::set<int>& data, std::istream& file)
{
    std::copy(std::istream_iterator<int>(file),
              std::istream_iterator<int>(), 
              std::inserter(data, data.end()));
}

clock() 中的时间平均超过 3 次运行(正常)和 3 次运行(-O4)

                    Plain Data
           Normal              -O4
           =========           ========= 
t1 Result: 21057300            6748061
t2 Result:  6580081            4752549
t3 Result:  6675929            4786003
t4 Result:  8452749            6460603

结论一:对于排序好的数据:

Best:   data.insert(data.end(), <item>)  // Hint end()
Worst:  data.insert(<item>);             // No Hint

结论 2:优化很重要。

【讨论】:

  • 我会直接使用set的迭代器构造函数。
【解决方案3】:

这系列可能正在重新平衡。你真的有多少项目需要 5.6 分钟?如果您的项目集足够大,您可能会遇到物理 RAM 限制和抖动,或者只是有非常糟糕的缓存未命中。

绝对没有办法禁用重新平衡。如果可以,那么该集合将能够破坏其不变量,这将是不好的。

  • 获取分析器并分析您的代码,而不是猜测需要花费什么时间。
  • 您是否尝试过使用end 而不是之前的迭代器作为另一个数据点的两个参数插入?
  • 您是否尝试插入预先保留的vector 来比较时间?
  • 您能否摆脱其他容器类型,例如堆或(排序的)向量?
  • 如果您可以快速加载到向量中,请执行此操作,然后 random_shuffle 它,然后尝试再次插入到集合中,看看会发生什么。

【讨论】:

  • 我想我需要一套,因为我有很多查找发生。排序向量是一种可能性(对其进行二进制搜索),但我可能也必须进行动态插入。因此,如果我可以在初始加载时解决此问题,那么一组似乎更可取。
  • 关于其他问题:使用 end() 的双参数插入大致相同,就像插入预先保留的向量后跟 insert(Vect.begin(), Vect.end() )。我会试试 random_shuffle。
猜你喜欢
  • 2018-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-28
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
相关资源
最近更新 更多