【问题标题】:Is it possible to apply std::sort on std::unique<T[ ]>?是否可以在 std::unique<T[ ]> 上应用 std::sort ?
【发布时间】:2016-08-10 13:07:34
【问题描述】:

假设我有一个想要排序的动态数组,我可以这样做

std::vector<int> v(100);
for (int i = 0; i < 100; i++) v[i] = rand();
std::sort(v.begin(), v.end());

但是对于性能关键的代码,初始化开销是不可接受的,更多细节在https://stackoverflow.com/a/7269088/3667089

我也可以

int *v = new int[100];
for (int i = 0; i < 100; i++) v[i] = rand();
std::sort(v, v + 100);

但必须自己管理内存必然会导致大型代码库中的内存泄漏。

所以看来最可行的办法是

std::unique_ptr<int[]> v(new int[100]);
for (int i = 0; i < 100; i++) v[i] = rand();
std::sort(v, v + 100);

没有初始化开销,也不需要担心内存管理,但这会返回一个长编译错误。有人可以告诉我我做错了什么吗?

我在 Ubuntu 14.04 上,GCC 作为编译器。

编辑:更改代码,使数据尚未排序

【问题讨论】:

  • std::sort(v.get(), v.get() + 100)?
  • @KerrekSB 哇,真是又快又准。如果你写一个答案,我会接受它。
  • (顺便说一下,你的数据已经排序了……)
  • 您分配了多少这些项目?拥有一个从预先分配的大块内存中提取的自定义分配器可以解决您的问题。请注意,您使用的 GCC 版本通常比所涉及的任何操作系统都更相关。
  • 那么您要解决的真正问题是向量的快速初始化?

标签: c++ c++11 heap-memory unique-ptr


【解决方案1】:

std::sort 仍然需要迭代器,而unique_ptr 不是迭代器。但是,它确实保留了可以用作一体的东西:它的指针:

std::sort(v.get(), v.get() + 100);

std::sort(&*v, &*v + 100);

std::sort(&v[0], &v[0] + 100); // N.B. &v[100] invokes undefined behavior

但是您真正想要的是一个默认初始化而不是值初始化的vector 分配器。这就是性能差异的来源 - 使用 std::vector 和默认分配器将首先对所有 ints 进行零初始化,然后为它们分配一些值,而您的其他选项没有这个额外的零初始化步骤。

查看Casey's 这样的实现,然后就这样做:

std::vector<int, default_init_allocator<int>> v(100); // not zero-initialized
for (int i = 0; i < 100; i++) v[i] = i;
std::sort(v.begin(), v.end());

另一种方法更简单,因为您不必处理分配器(尽管在代码方面更烦人)是为 int 引入一个包装器,其值初始化不会做任何事情:

template <class T>
struct Wrapper {
    Wrapper() { }
    T value;
};

std::vector<Wrapper<int>> v(100);              // not zero-initialized
for (int i = 0; i < 100; i++) v[i].value = i;  // but have to do this... 

请注意,仅使用 reserve()push_back() 是不够的 - 与在默认初始化后简单地按索引分配相比,如果您对延迟敏感到足以询问这个问题,可能很重要。

【讨论】:

  • 选项 2 很有趣。 :)
  • 这里有一点demo 非初始化分配器如何影响代码生成。
  • 注意:如果*v 有一个重载的operator &amp;,则 2 和 3 都会失败,因此不应在泛型代码中使用。道德:通用代码是不可能编写的。
  • 我刚刚测量了时序,速度方面的原始指针 > casey 的默认向量 > unique_ptr > std 向量,我不太明白为什么,因为我希望原始指针 = casey 的默认向量 = unique_ptr > std 向量...
  • 选项3是UB;它打破了operator[] 上的Requires
【解决方案2】:

阅读问题中的链接,如果 vector 没有为每个元素调用不必要的构造函数,您似乎会很高兴。有一些技术可以消除这种开销。

std::vector<int> v;
v.reserve(100);
for (int i = 0; i < 100; i++) v.emplace_back(rand());
std::sort(v.begin(), v.end());

【讨论】:

  • 有些检查会减慢 push_back 的速度,见stackoverflow.com/a/20168172/3667089
  • @user3667089,当您能够测量一个分支的性能影响(这将在第 4 次迭代时被分支预测消除)和非原子增量时,请致电给我。
  • @mark 遗憾的是,std::vector 中没有 emplace_back_I_know_there_is_capacity 方法,而且我还没有看到编译器在任何分析测试(或任何 ASM 输出)中解决这个问题。也许你可以证明我错了,我至少有一年没找了。
  • @SergeyA 您可以轻松衡量性能影响。如果你写了一个基准,你会发现有很大的不同。
  • @Barry 如果初始化很简单,例如常量或递增值,则可能为真。如果有任何工作正在生成值,例如问题中的rand,则差异很快就会变得微不足道。是的,它可能仍然是可测量的,如果需要,C++ 仍然允许您写入裸机。这个答案只是试图突破你可以在没有英勇努力的情况下实现的目标。
猜你喜欢
  • 1970-01-01
  • 2020-11-03
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 2023-02-16
  • 1970-01-01
  • 2017-05-14
相关资源
最近更新 更多