【问题标题】:How fast is std::swap for integer types?整数类型的 std::swap 有多快?
【发布时间】:2013-08-19 16:38:36
【问题描述】:

STL 实现了一个通用的std::swap 函数来交换两个值。可以通过以下方式呈现:

template <class T> void swap (T& a, T& b)
{
  T c(std::move(a));
  a=std::move(b);
  b=std::move(c);
}

但是,有一个 XOR 交换算法来交换 2 个整数 (http://en.wikipedia.org/wiki/XOR_swap_algorithm):

void swap_u( size_t& x, size_t& y )
{
   x = x^y;
   y = x^y;
   x = x^y;
}

我的问题:

  1. 现在是优化吗(x86arm)?
  2. C++ 标准是否支持这种优化?
  3. 是否有任何真正的 STL 实现具有 std::swap 整数专用化?

【问题讨论】:

  • XOR 交换不一定有效 - 它比有用的优化更新颖 - 只需使用临时变量,保持简单并避免噱头 - 让编译器做聪明的事情。
  • 如果 x 和 y 指向相同的内存地址,则第二个实现将不起作用(如果您传递具有可变索引的数组元素,则可能发生)。
  • 智能编译器了解std::swap 在做什么,并且可以简单地注意到变量可以在不同的寄存器或地址中引用。这是一种有效的无成本交换。如果您查看生成的程序集,您可以看到这一点。
  • 这里的重要教训:仅仅因为某些东西看起来很复杂和hackish并不自动意味着它更快。 :)
  • @Blastfurnace 这甚至不需要(总是)理解std::swap 的编译器,甚至不需要非常智能的编译器——不必要的寄存器移动通常会通过相当简单的窥视孔优化和寄存器调度来消除. (另一方面,XOR 技巧需要更智能的编译器来优化)。

标签: c++ performance c++11 stl swap


【解决方案1】:

在绝大多数情况下,XOR 交换不是优化。

看到这个wiki entry

在大多数实际场景中,使用临时寄存器的普通交换算法效率更高。 XOR 交换可能可行的有限情况包括:

  • 在指令集编码允许 XOR 交换以较少字节数编码的处理器上;
  • 在具有高寄存器压力的区域中,它可能允许寄存器分配器避免溢出寄存器。
  • 在可用 RAM 非常有限的微控制器中。

由于这些情况很少见,大多数优化编译器不会生成 XOR 交换代码。

另请注意,您的 XOR 交换实现已损坏。您需要首先检查 x 和 y 是否没有别名。这个检查肯定会让 XOR 交换变慢。

我不知道任何使用 XOR 交换的标准库实现。

请注意,无论标准库实现什么,如果 XOR 交换确实比普通交换快,那么优化编译器将执行 peephole optimization 将其转换为 XOR 交换。这确实是让编译器为您选择的情况。

【讨论】:

  • 我们在这里学到了一个很好的教训:Freaky-tricky-wiki 代码不可能是优化
【解决方案2】:

XOR 交换实际上只是一个噱头,在某些情况下可能会失败(例如,两个变量都是对同一对象的引用)。

XOR 交换也不是特别有效,因为它具有串行依赖关系,因此它总是需要至少三个指令周期。使用临时交换的直接交换具有较少的依赖关系,允许在现代超标量 CPU 上实现一些并行性 - 在某些 CPU 上,它甚至可以在一条指令中实现,但即使没有特殊指令,它也可能在两个周期内执行。

【讨论】:

    【解决方案3】:

    在 X86 上,内存位置(不是 CPU 寄存器)之间的三重 XOR 交换与三重复制占用相同的处理器周期。如果临时是寄存器,它们可能会更少。

    【讨论】:

      【解决方案4】:

      正如在大多数情况下已经解释的那样,XOR 位摆弄会更慢。

      但这也很大程度上取决于周围的代码。 假设这种交换是单独完成的,远离任何其他需要这些值的代码(因此它们不会加载到寄存器中),我们在这里使用“普通”x86 处理器。

      任何交换这 2 个值的算法至少需要 2 次操作来将内存中的值加载到寄存器中,另外需要 2 次操作来再次将这些值存储到内存中(x86 没有操作来交换 2 个内存位置的内容直接)。

      当使用像这样的临时变量时:

      void swap (int& a, int& b)
      {
        int temp = a;
        a = b;
        b = temp;
      }
      

      基本上任何编译器都会认识到“temp”仅在本地用于交换并且不会给它一个内存位置。 而且由于它只保存 'a' 的值,它甚至不会是一个单独的寄存器。

      它的汇编代码看起来像这样(伪汇编):

      load a to rA
      load b to rB
      store rA to b
      store rB to a
      

      因此,在大多数情况下,就内存访问、指令数量和寄存器数量而言,这将是最有效的。

      只有当编译器无法识别“temp”没有用于其他任何事情并将其存储在单独的寄存器中(或该死的实际内存)时,XOR 变体才能在任何事情上更有效。

      但这仍然是理论上的,因为您的交换将被其他代码包围,而这将在那里更为重要。如果这些值不再使用,那么整个交换将被忽略。如果在其他计算之后直接使用这些值,那么可能只是以下代码交换了 2 个寄存器,因此它自身的交换有 0 条指令。而且你真的很难找到任何比无事可做更有效的解决方案。

      当然还有其他更晦涩的指令集,它们可能包含直接交换 2 个内存位置内容的指令。

      【讨论】:

        猜你喜欢
        • 2021-10-01
        • 2015-09-30
        • 2017-06-18
        • 1970-01-01
        • 2020-04-22
        • 2019-10-14
        • 2018-11-08
        • 1970-01-01
        • 2011-04-02
        相关资源
        最近更新 更多