【问题标题】:Why std::sort construct objects? [duplicate]为什么 std::sort 构造对象? [复制]
【发布时间】:2017-08-10 21:40:41
【问题描述】:

我创建了以下类来了解std::sort的行为:

class X {
public:
  X(int i) : i_(i) { }
  X(X&& rhs) noexcept : i_(std::move(rhs.i_)) { mc_++; }
  X& operator=(X&& rhs) noexcept {
    i_ = std::move(rhs.i_); ao_++; return *this;
  }
  void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); sw_++; } 
  friend bool operator<(const X& lhs, const X& rhs) {
    return lhs.i_ < rhs.i_;
  }
  static void reset() { mc_ = ao_ = sw_ = 0; }
private:
  int i_;
  static size_t mc_, ao_, sw_; // function-call counters
};
// void swap(X& lhs, X& rhs) { lhs.swap(rhs); }

并运行以下基准代码:

int main() {
  std::vector<X> v;
  for (int i = 0; i < 1000000; i++) v.emplace_back(i);

  std::mt19937 g(0xa41bc9); // fixed seed to compare measurements
  std::shuffle(v.begin(), v.end(), g);

  X::reset();
  std::sort(std::begin(v), std::end(v));
}

在线IDE中的完整代码在这里:https://wandbox.org/permlink/nbwRKptakgCSHK4f

测量的特定函数的调用次数如下(均带有-O2/O2标志):

   function:   move ctor    operator=        swap
  GCC 7.1.0:   5,007,335   11,700,048           0
clang 4.0.0:   4,932,061    9,973,899           0
 MSVC 19.11:   8,580,356   21,521,211           0

如果我取消注释 swap 函数,情况会好转:

   function:   move ctor    operator=        swap
  GCC 7.1.0:     999,999    3,685,376   4,007,336
clang 4.0.0:      72,554      254,885   4,859,507
 MSVC 19.11:     906,593    6,173,685   7,673,763

但是,仍然仍然有很多移动构造函数(加上析构函数)和移动赋值运算符的调用。困扰我的是效率。例如,swapoperator= 的调用可以由编译器内联,但我猜编译器可能不会“内联”(优化)对象的构造/销毁

为什么在排序中使用对象的构造/赋值?就地排序(通常std::sort 会这样做)可以纯粹通过比较和交换操作来实现。

更新

我的假设是错误的。优化对象创建/销毁似乎完全合法,例如:

X temp = std::move(x1);  
x1 = std::move(x2);
x2 = std::move(temp);

因此,这样的代码可以像自定义swap 一样高效。在线示例:https://godbolt.org/g/ud4u9U - 没有调用移动构造函数/赋值运算符,尽管这些并不重要,它们的功能已内联到 main

【问题讨论】:

  • 不要认为这是重复的。 “为什么std::sort不完全由swap实现?”在引用的帖子中没有任何内容。
  • "swap 和 operator= 的调用可以由编译器内联,但我猜编译器可能不会“内联”(优化)对象的构造/破坏” - 你为什么会这样?
  • “例如,swapoperator= 的调用可以由编译器内联,但我猜编译器可能不会“内联”(优化)对象的构造/销毁。” -- 为什么编译器不能内联构造函数和析构函数?
  • @DanielLangr 如果你同意这是骗子告诉我,我可以锤击。

标签: c++ sorting swap move-constructor move-assignment-operator


【解决方案1】:

std::sort() 是一种混合算法。虽然普通的快速排序只能使用swap() 操作(来自std::partition()),但真正的排序方法很可能也使用插入、堆和/或合并排序。对于这些排序算法,通常更有效的方法是将对象移开(通过移动构造),将对象移入当前“洞”,最后将不合适的对象移开。 可能只保留一个临时对象是合理的,但很可能算法使用了一些函数,并且保留临时对象有点不切实际(尽管我之前没有考虑过并且可能值得尝试)。

今年早些时候,我的 Quicker Sorting 谈话记录在 Italian C++ Conference:它详细介绍了快速排序的细节。

结果是:如果要对对象进行排序,最好确保复制/移动构造、复制/移动赋值、析构函数和swap() 速度很快。我可以想象,保留一个临时对象可以减少构造和销毁的需要,但任务仍然存在。一个专门的破坏性动作也可能会提高性能,但我还没有尝试过。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多