【问题标题】:most efficient way of swapping values c++交换值的最有效方法c ++
【发布时间】:2016-05-11 07:40:39
【问题描述】:

我想知道在操作方面最有效的交换整数的方式是在 c++ 中,为什么?是这样的:

int a =..., b = ...;
a = a + b;
b = a - b;
a = a - b;

比使用临时更有效?还有其他更有效的方法吗? (不只是要求交换整数的其他方法)以及为什么它们会更有效?

【问题讨论】:

  • 我建议std::swap
  • 在现代机器上,这可能是交换整数最慢的方法。如果您有一台带有两个寄存器的机器,那可能是个好主意,特别是如果它有一个鼓存储器。

标签: c++ performance int double processing-efficiency


【解决方案1】:

赋值总是比算术运算快。

C++ implementation for std::swap 是

template<typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1); // or T temp(std::move(t1));
    t1 = std::move(t2);
    t2 = std::move(temp);
}

所以使用临时变量比做算术技巧更好。
并且使用 std::swap 更好,因为在编程中重新发明轮子从来都不是一个好主意

【讨论】:

  • 这是一个可能的实现,是的。但不一定是整数。这只是一个合理的默认值。
  • 也可以是t1 = std::exchange(t2, t1);
【解决方案2】:

最好的方法是信任您的编译器使用 C++ 标准库函数。它们是为彼此设计的。

std::swap 会赢。

您可以对 int 使用 XOR 交换(不需要临时),但现在它的性能仍然不如 std::swap

【讨论】:

  • 好的,谢谢,没有意识到标准函数会比几行代码更快。
  • 我要补充一点,它的性能不如std::swap,因为std::swap 可能会在某些架构上使用单个机器指令进行交换。
  • @MaraJade 我的经验法则是尝试使用标准提供的函数/结构。如果您分析并发现它们的性能不够,请寻找替代品。
  • 另请注意,在极少数情况下,手写代码的性能优于执行相同操作的标准库函数,您很可能发现了性能错误。所以在这种情况下不要害怕联系你的编译器编写者/标准库维护者。
  • 如果您不小心尝试与自身交换值,XOR 交换会失败。
【解决方案3】:

在我的例子中,std::swap 比下面的慢 5%(都使用 O3 优化)。一般来说,std::swap() 函数调用复制构造函数可能总是比只复制部分内存要慢。

#include <cstring>

size_t objectSize = sizeof(Object);
char temp[objectSize];

loop {
    loop {
        memcpy(temp, a, objectSize);
        memcpy(a, b, objectSize);
        memcpy(b, temp, objectSize);
    }
}

编辑:使用堆栈而不是堆内存分配。

【讨论】:

  • 我也可以用它来交换uint64_t几百万次,还是只对大对象元素有用?
  • 我认为,在这种情况下,标准的值交换会更快。但是你必须尝试一下。
  • 但是memcpy会破坏c++中的对象一致性。
  • @Qwertiy 你能否解释一下对象一致性将如何被破坏?
【解决方案4】:

最有效的方法是不要自己尝试。 这真的取决于你为什么/想要这样做。试图聪明并用 C++ 编写晦涩难懂的代码只会降低编译器正确优化它的机会。

假设我们使用您编写的 ±-方式: 首先必须从内存中加载值 a 和 b。 然后你正在做 3 个算术运算来“交换”它们的内容。 最后,这两个值必须再次存储在内存中。 (不会使用实际的汇编代码,因为我不太熟悉它,而且这个伪汇编更容易理解这个概念)

load a into register rA
load b into register rB
add rB to rA and store in rA
subtract rB from rA and stor in rB
subtract rB from rA and store in rA
store register rA to memory b
store register rB to memory a

如果编译器会完全按照您的要求进行操作(很可能他会忽略它并使其变得更好),那就是: 2 个负载,3 个简单的数学函数,2 个存储 - 7 个操作。

它也可以做得稍微好一点,因为加法/减法可以用内存中的 1 个值来完成。

load 'a' into register rA
add b to rA and store in rA
subtract b from rA and store in rB
subtract rB from rA and store in rA
store rA to a
store rB to b

如果我们使用额外的 tmp 变量:

int a =..., b = ...;
int tmp = a;
a = b;
b = tmp;

编译器可能会认识到“tmp”只是一个临时变量,仅用于交换 2 个值,因此它不会为其分配内存位置 btu 仅使用寄存器。 在这种情况下,它会做的事情是:

load a into register rA
load b into register rB
store register rA to memory b
store register rB to memory a

只有 4 个操作 - 基本上是最快的,因为您需要加载 2 个值,并且您需要存储 2 个值,仅此而已。 (对于现代 nx86_64 处理器,没有命令可以只交换内存中的 2 个值 - 其他架构可能有它,并且在这种情况下更快)。

执行这些算术运算(或 xor-trick)是一种很好的练习,但在现代 x86 CPU 上,除了最基本的编译器之外,它不会以任何形式“更高效”。 它将使用相同数量的寄存器,相同数量的变量内存,但需要更多指令来完成相同的工作。 一般来说,除非您检查过您的代码、对其进行测试和基准测试并发现生成的程序集不如预期的那么好,否则您不应试图超越编译器。

但几乎从不需要达到那个级别来进行优化,您最好把时间花在看大局上。

【讨论】:

    【解决方案5】:
    #include <iostream>
    using namespace std;
    
    void swap(int &a, int &b){
        b = (a+b) - (a=b);
    }
    
    int main() {
        int a=1,b=6;
        swap(a,b);
        cout<<a<<b;
        return 0;
    }
    

    【讨论】:

    • 这是未定义的行为。
    猜你喜欢
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 2011-09-14
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    • 2012-11-06
    相关资源
    最近更新 更多