【问题标题】:Why swap doesn't use Xor operation in C++为什么交换在 C++ 中不使用 Xor 操作
【发布时间】:2012-05-19 22:04:58
【问题描述】:

我了解到可以使用异或操作来实现有效的交换功能。像这样:

template<class T>
void swap(T& a, T& b)
{
    a = a^b;
    b = a^b;
    a = a^b;
}

但是我在网上能找到的swap的实现基本上是这样的:

template<class T>
void swap(T& a, T& b)
{
    T temp(a);
    a = b;
    b = temp;
}

编译器似乎没有为上述两种形式生成相同的代码,因为我在 VC++ 2010 上对其进行了测试,第一个比std::swap 更快地完成了这项工作。第一个有便携性或任何其他问题吗?请随时纠正我的任何错误,因为我不是英语母语,也不擅长 C++。

(编者注:很可能测试是使用未优化的调试版本完成的,而不是 std::swap 可以内联的发布版本。基准调试版本毫无意义。编译器通常不会优化 xor-swap 到更多的东西效率。)

【问题讨论】:

  • 只是一个假设:至少 x86 CPU 有XCHG 指令,比三个 XOR 更快。
  • 您是否在启用优化的情况下运行测试?
  • @Joulukuusi x86 上的 XCHG 指令从来都不是交换替换。如果在内存操作数上使用它有一个隐式锁定前缀,它被用作同步工具。 XCHG reg, reg 可能会被使用,尽管我怀疑它是否需要 - 重命名寄存器更快。我已经写了几 k 行汇编,我从未有过使用 xchg 交换值的冲动。
  • @phresnel 天哪,我发现我从来没有接受过答案,因为我不知道如何接受。
  • @drhirsch,谢谢!事实证明,gcc 在我的机器上将第二个交换函数优化为几个movs。

标签: c++ swap xor micro-optimization premature-optimization


【解决方案1】:

我了解到可以使用异或操作来实现有效的交换功能

恐怕你学错了。 XOR 交换已过时:如果它比使用临时值可靠地快,那么它不应该出现在现代编译器和处理器上(“现代”我的意思是大约过去 20 年或更长时间)。你说它对你来说更快,也许你应该展示你的基准代码,看看其他人是否得到相同的结果。

除了您的代码仅适用于整数类型这一事实之外,它还有一个基本错误。用你的交换版本试试这个:

int a = 1;
swap(a,a);
std::cout << a << '\n';

【讨论】:

    【解决方案2】:

    而且效果取决于你在哪里使用它。

    在普通 cpu 上,两个整数变量的正常交换看起来像

    $1 <- a
    $2 <- b
    a <- $2
    b <- $1
    

    4 次操作,2 次加载,2 次存储,最长依赖为 2

    异或方式:

    $1 <- a
    $2 <- b
    $3 <- $1 ^ $2
    $4 <- $3 ^ $2
    $5 <- $3 ^ $4
    a <- $5
    b <- $4
    

    7 个操作,2 个加载,2 个存储,3 个逻辑,最长依赖为 4 个

    因此,即使适用,至少通常使用 xor 进行交换也会更慢。

    【讨论】:

    • 使用现代 SSA 编译器,第一个更简单:Rename(a1,b2), Rename(b1,a2) - 不是一条 CPU 指令,而只是编译器的簿记工作。哪个变量进入了哪个寄存器?
    【解决方案3】:

    我认为最明显的原因是 XOR 运算符只对整数类型有意义。

    【讨论】:

    • 你总是可以为合适的类型提供重载/专业化。
    • 不过,你可以做邪恶的演员表。
    • @phresnel 不在标准中,我希望 :)
    • 你可以自己添加专业化,所以不需要标准库来做。他们当然可以,但不清楚这是否真的值得付出努力。
    • @deong 不,不能。您只能专门针对用户定义的类型。不过,只有标准库的实现者可以。
    【解决方案4】:

    当然,因为 xor 技巧适用于 POD 类型。

    如果您想交换两个用户定义的复杂类型,xor 将不起作用。你需要一个深拷贝,而不是原始内存的直接拷贝,就像xor 所做的那样。

    编辑:

    我在 VC++ 2010 上对其进行了测试,第一个完成工作的速度更快(并且比 std::swap 更快)。

    真的吗?你是在调试模式下编译的吗?你的结果是什么?

    【讨论】:

      【解决方案5】:

      首先,XOR 运算符只为整数类型定义。

      其次,您可以使用强制转换技巧将非整数类型转换为整数形式。

      但第三,对于除 POD 类型之外的所有类型,这都会导致未定义的行为,

      第四,对于 XOR 操作没有得到很好支持的大小/对齐方式的类型,需要更多的操作(循环是最不坏的)。

      您可以重载operator^,但这意味着swap() 的每个特化都必须确保它存在或定义一个,这可能会在名称查找时产生比值得的更多的混乱。当然,如果这样的操作符已经存在,它不一定有正确的行为,你可能会得到更差的性能,因为这样的重载不一定是inlineconstexpr

      【讨论】:

        猜你喜欢
        • 2012-07-04
        • 2011-04-24
        • 2021-04-30
        • 1970-01-01
        • 2011-12-20
        • 1970-01-01
        • 2011-07-31
        • 2010-11-25
        相关资源
        最近更新 更多