用std::move执行就地排列

Performance of In-place permutation with std::move

本文关键字:排列 move std 执行      更新时间:2023-10-16

我写了一个就地排列算法[TAOCP3中的一个练习],其中内环是

template<typename T>
void inplace_permute(T *pT, int *P, const int n)
{
  // part of the inner loop
  pT[j] = std::move(pT[k]);
  P[j] = j;
  // more logic to update j, k, etc.
}

这里,pT是要排序的元素的阵列,P是排列表,n是元素的数量。

如果T是复杂类型,例如字符串,std::move会提高性能吗?同样重要的是,如果T是基元类型(例如int?)

,它是否可以优化

我将您的问题总结为:

std::move做什么?

基本上,std::move可以使用Move构造函数和Move Assignment运算符。

通常,它们接近于按位复制(它们是而不是恰好一个),因此性能通常与类的sizeof有关。

因此,如果std::move(someint)std::move(somestring)具有相似的大小,即使其中一个是内置类,另一个是用户类,它们也会具有相似的性能。

不过也有一些不同。

  • 在内置设备上,移动只是按位复制。由于移动自值未指定,因此不需要归零。移动后,您可能希望为其指定一个已知值(0或其他值)
  • 对于通常具有资源(这样一个动态分配的缓冲区)的用户类,移动意味着:清理已移动到的实例(用于分配),制作一个有点位的副本,重置已移动的实例。所以还有更多的工作要做

为了理解这一点,我们可以用一个字符串实现示例来说明:

class String {
public:
  // Many things
  String(String&& right);
  String& operator=(String right);
  friend void swap(String& left, String& right);
private:
  // On 64 bits platform, 4x as big as an `int`
  size_t capacity;
  size_t size;
  char* buffer;
};
// Move Constructor
String::String(String&& right):
   capacity(right.capacity), size(right.size), buffer(right.buffer)
{
  right = String(); // reset right
}

// Assignment Operator
String& String::operator=(String right) {
  swap(*this, right);
  return *this;
}
// Swap
void swap(String& left, String& right) {
  using std::swap;
  swap(left.capacity, right.capacity);
  swap(left.size    , right.size);
  swap(left.buffer  , right.buffer);
}

正如您所看到的,分配pT[j] = std::move(pT[k]);的意思是(语义上):

  • 创建临时(制作pT[k]的逐位副本)
  • 重置pT[k]
  • 在临时和pT[j]之间交换状态
  • 销毁临时存储(通常释放从pT[j]继承的存储)

编译器应该或多或少能够将其优化为:

  • pT[j]pT[k]之间交换状态
  • 销毁pT[k](只释放存储)
  • pT[k]中重建新实例

或者,粗略地说:

swap(ptj, ptk);    // swap 3 fields
delete ptk.buffer; // might be a no-op
ptk = String();    // 0-out 3 fields

注意:这是一个玩具实现,在gcc上会更简单,在VC++上会更复杂,因为它们使用不同的数据表示。

如果T有一个移动赋值运算符,它将用于右值,并可能提高性能。

如果类型没有移动赋值,但有复制赋值(如int),则会复制该值。