在 C 中交换值的最快方法是什么?

What is the fastest way to swap values in C?

提问人:JawnV6 提问时间:8/31/2008 更新时间:1/14/2020 访问量:65822

问:

我想交换两个整数,我想知道这两种实现中哪一种会更快: 使用临时变量的明显方法:

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

或者我相信大多数人都看过的 xor 版本:

void swap(int* a, int* b)
{
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

似乎第一个使用了一个额外的寄存器,但第二个正在执行三个加载和存储,而第一个只执行两个。有人可以告诉我哪个更快,为什么?为什么更重要。

C 性能

评论

2赞 fider 9/21/2017
XOR 速度较慢。使用 godbolt 检查这两个函数的汇编程序指令计数。请注意,如果您将对值使用异或方法而不是存储在指针下的值,则速度是相同的(至少对于 GCC 编译器)
1赞 teknoraver 11/13/2019
godbolt.org/z/nqVb9q
3赞 Andrew Henle 3/18/2020
似乎第一个使用了额外的寄存器这里已经晚了一点,但为什么会有人这么想呢?认为位调整比使用临时变量更快的信念忽略了大多数计算机在单独的 CPU 和内存下如何工作的现实。使用临时变量的交换可能实现为“将 A 加载到寄存器 1 中,将 B 加载到寄存器 2 中,将寄存器 1 保存到 B,将寄存器 2 保存到 A”。“将两个变量加载到寄存器中,摆弄一下,然后执行两个保存操作”较慢。您必须同时加载并保存两者,一路上的位摆弄是无关紧要的。

答:

112赞 caramelcarrot 8/31/2008 #1

数字 2 经常被引用为“聪明”的方式。事实上,它很可能更慢,因为它掩盖了程序员的明确目标 - 交换两个变量。这意味着编译器无法对其进行优化以使用实际的汇编程序操作进行交换。它还假定能够对对象执行按位异或操作。

坚持第 1 点,它是最通用和最容易理解的交换,可以很容易地模板化/通用化。

这个维基百科部分很好地解释了这些问题:http://en.wikipedia.org/wiki/XOR_swap_algorithm#Reasons_for_avoidance_in_practice

评论

0赞 Dan Lenski 10/1/2008
点对点。一般来说,最好向编译器说明你的目标,而不是试图欺骗它做你想做的事。与临时变量交换是一种常见的操作,任何像样的编译器都可以无情地优化它。
2赞 3/6/2009
我完全同意。此外,如果价值交换确实是一个瓶颈(通过测量证明),并且无法避免,请实施您能想到的所有方法来做到这一点,并衡量哪种方法对您(您的机器、操作系统、编译器和应用程序)来说更快。对于低级的东西,没有通用的答案。
0赞 warren 9/9/2009
我的印象是,至少在 x86 上,实际上只是连续调用了三个swapxor
0赞 Peter Cordes 8/5/2014
@warren:xchg %eax,%eax 字面意思就是标准的单字节 NOP 指令代码。它不会将 %eax 归零,因此它没有使用 xor。
0赞 warren 8/6/2014
@PeterCordes - 为什么 %eax 需要归零?
15赞 Sander 8/31/2008 #2

第一种速度更快,因为按位运算(如异或)通常很难为读者可视化。

当然,理解起来更快,这是最重要的部分;)

5赞 17 of 26 8/31/2008 #3

真正知道的唯一方法是测试它,答案甚至可能因您使用的编译器和平台而异。如今,现代编译器非常擅长优化代码,除非你能证明你的方式真的更快,否则你永远不应该试图超越编译器。

话虽如此,你最好有一个很好的理由选择#2而不是#1。#1 中的代码更具可读性,因此应始终首先选择。只有当你能证明你需要做出改变时,才切换到#2,如果你这样做了 - 评论它以解释发生了什么以及为什么你以不明显的方式这样做。

作为一个轶事,我和几个喜欢过早优化的人一起工作,这使得代码非常可怕,无法维护。我也敢打赌,他们经常搬起石头砸自己的脚,因为他们以一种非直接的方式编写代码,从而阻碍了编译器优化代码的能力。

93赞 Ant 9/1/2008 #4

如果 a 和 b 指向同一地址,则 XOR 方法将失败。第一个异或将清除两个变量指向的内存地址上的所有位,因此一旦函数返回 (*a == *b == 0),无论初始值如何。

在 Wiki 页面上的更多信息:XOR 交换算法

虽然不太可能出现这个问题,但我总是更喜欢使用保证有效的方法,而不是在意外时刻失败的聪明方法。

评论

3赞 user9282 9/20/2008
通过添加条件 *a != *b 来防止混叠非常容易。
35赞 Matt Curtis 1/22/2009
然后你的交换函数有一个分支。尽管这是一个愚蠢的问题,但如果 OP 追求速度,那么引入分支可能是一个坏主意。
8赞 configurator 2/4/2009
@mamama,它应该是 a != b 而不是 *a != *b;失败是指地址相同,而不是值相同。
1赞 Greg Rogers 3/6/2009
它可以是其中之一 - 如果值已经相同,则无需交换。但是检查 (a != b) 更有意义。
13赞 vonbrand 2/2/2013
如果有一些聪明的技巧可以加快速度,那么你的邻里编译器已经听说过它,并且正在背后使用它。这种微优化(特别是手动完成)今天不会给你带来任何东西,内存访问比执行指令慢得多。为了“性能”而混淆代码会损害等式中最昂贵的部分:程序员时间。
0赞 Tim Ring 9/1/2008 #5

如果您可以使用一些内联汇编程序并执行以下操作(psuedo 汇编程序):

PUSH A
A=B
POP B

您将节省大量参数传递和堆栈修复代码等。

评论

0赞 Joao Vilaca 1/12/2009
注意:VC++ 不允许在 64 位模式下内联 ASM。希望它相关或被理解为如此:)
0赞 Peter Cordes 8/5/2014
这交换了两个寄存器的内容,而不是它们指向的位置。内联 ASM 还使编译器的优化能力大大降低,因此除非您为 SSE 指令执行此操作,或者您的内联 ASM 包含内部循环,否则这是不值得的。
0赞 Palle 11/4/2015
在程序集中,还有 xchg 命令,用于交换两个值。
0赞 Tim Ring 10/11/2016
吹毛求疵是为了什么......1) 伪装代码,我不是从字面上吐出寄存器“A”等等。2) 同样,伪代码,不引用任何特定的汇编程序 (xchg)。3) 许多人不使用 64 位 vc++ (aaargh)。
8赞 Nir 9/1/2008 #6

你优化了错误的东西,这两者都应该如此之快,以至于你必须运行它们数十亿次才能获得任何可衡量的差异。

几乎任何事情都会对你的性能产生更大的影响,例如,如果你正在交换的值在内存中接近你触摸的最后一个值,它们就会在处理器缓存中,否则你将不得不访问内存 - 这比你在处理器内执行的任何操作都慢几个数量级。

无论如何,你的瓶颈更有可能是低效的算法或不适当的数据结构(或通信开销),而不是你如何交换数字。

3赞 Andrew O'Reilly 9/3/2008 #7

如前所述,要回答您的问题,需要深入研究此代码将在其上运行的特定 CPU 的指令时序,因此需要我围绕系统中缓存的状态和编译器发出的汇编代码做出一系列假设。从了解您选择的处理器实际工作方式的角度来看,这将是一个有趣且有用的练习,但在现实世界中,差异可以忽略不计。

-1赞 paperhorse 9/5/2008 #8

我只是将两个交换(作为宏)放在我一直在玩的手写的快速排序中。XOR版本(0.1秒)比带有临时变量的版本(0.6秒)快得多。然而,XOR确实破坏了数组中的数据(可能与Ant提到的地址相同)。

由于它是一个胖的枢轴快速排序,XOR 版本的速度可能是由于使数组的大部分相同。我尝试了最容易理解的第三个版本的交换,它与单个临时版本的时间相同。


acopy=a;
bcopy=b;
a=bcopy;
b=acopy;

[我只是在每个交换周围放置了一个 if 语句,因此它不会尝试与自身交换,并且 XOR 现在与其他 XOR 花费相同的时间(0.6 秒)]

评论

3赞 unwind 3/10/2009
我喜欢这个评价!“它的速度更快,但它确实损坏了数据。经典。
43赞 Skizz 9/5/2008 #9

在现代处理器上,在对大型数组进行排序时,可以使用以下方法,并且速度没有差异:

void swap (int *a, int *b)
{
  for (int i = 1 ; i ; i <<= 1)
  {
    if ((*a & i) != (*b & i))
    {
      *a ^= i;
      *b ^= i;
    }
  }
}

你的问题中真正重要的部分是“为什么?”部分。现在,回到 20 年前的 8086 天,上述内容将是一个真正的性能杀手,但在最新的 Pentium 上,这将是与您发布的两个相匹配的速度。

原因纯粹归结为内存,与CPU无关。

与内存速度相比,CPU 速度已经天文数字地上升了。访问内存已成为应用程序性能的主要瓶颈。所有交换算法都将花费大部分时间等待从内存中获取数据。现代操作系统最多可以有 5 个级别的内存:

  • 缓存级别 1 - 以与 CPU 相同的速度运行,访问时间可以忽略不计,但很小
  • 缓存级别 2 - 运行速度比 L1 慢一点,但更大,访问开销更大(通常,需要先将数据移动到 L1)
  • 缓存级别 3 - (并非总是存在)通常位于 CPU 外部,速度较慢且大于 L2
  • RAM - 主系统内存,通常实现一个管道,因此读取请求存在延迟(CPU 请求数据,消息发送到 RAM,RAM 获取数据,RAM 将数据发送到 CPU)
  • 硬盘 - 当没有足够的RAM时,数据被分页到HD,这真的很慢,而不是真正在CPU的控制之下。

排序算法会使内存访问变得更糟,因为它们通常以非常无序的方式访问内存,从而产生从 L2、RAM 或 HD 获取数据的低效开销。

因此,优化交换方法真的毫无意义 - 如果它只被调用几次,那么由于调用次数少,任何低效率都会被隐藏起来,如果它被调用了很多,那么任何低效率都会被隐藏,因为缓存未命中的数量(其中 CPU 需要从 L2(1 个周期)获取数据), L3(10 个周期)、RAM(100 个周期)、HD(!

您真正需要做的是查看调用 swap 方法的算法。这不是一项微不足道的工作。尽管 Big-O 表示法很有用,但对于小 n,O(n) 可能比 O(log n) 快得多。此外,许多算法都存在退化情况,即代码执行的操作超出了必要的范围(对几乎有序的数据使用 qsort 可能比使用提前检查的冒泡排序慢)。因此,您需要分析您的算法及其使用的数据。

这就引出了如何分析代码。探查器很有用,但您确实需要知道如何解释结果。永远不要使用单次运行来收集结果,始终在多次执行中平均结果 - 因为测试应用程序可能在中途作系统分页到硬盘。总是分析发布、优化构建、分析调试代码是没有意义的。

至于最初的问题——哪个更快?- 这就像试图通过观察后视镜的大小和形状来弄清楚法拉利是否比兰布尔吉尼更快。

评论

6赞 Ken White 3/6/2009
+1 表示不必要的优化提及。如果你实际上已经分析了你的代码,并且你最需要担心的是这两种交换一对整数的方法中哪一种更快,那么你已经编写了一个非常快的应用程序。在那之前,谁在乎掉期?
1赞 David Rodríguez - dribeas 7/22/2010
@Ken White:我同意,而且,如果分析显示大部分时间都花在交换上,那很可能是因为你交换了太多次(冒泡排序有人吗?),而不是缓慢地交换。
0赞 user 6/14/2013
除了硬盘比 RAM 慢得多之外,交换还意味着您需要执行一些完全不同的代码段,这些代码可能在 RAM 中,但几乎可以肯定不会在 L1 缓存中,也可能不在 L2 中(除非您严重缺乏 RAM 并不断交换)。因此,在完成任何有用的事情之前,CPU 需要获取内存管理器代码中实际执行交换的部分。
1赞 cmaster - reinstate monica 10/10/2013
虽然你的基本观点是正确的,但你展示的代码比问题中给出的两个版本要慢得多:Afaik,你在一个缓存行中得到四个,这意味着平均你得到不到 30 个周期的延迟来加载数据(不考虑预取),你的循环中有条件跳转(现代架构讨厌错误预测这些), 因此,你得到的不仅仅是每个循环迭代的一个周期。我敢打赌,您的交换至少需要 100 到 200 个周期,可能更多,但这在很大程度上取决于您交换的数字(有多少错误预测)。int
9赞 Harry 9/5/2008 #10

对于那些偶然发现这个问题并决定使用 XOR 方法的人。应考虑内联函数或使用宏来避免函数调用的开销:

#define swap(a, b)   \
do {                 \
    int temp = a;    \
    a = b;           \
    b = temp;        \
} while(0)

评论

2赞 Dan Lenski 10/1/2008
+1.当你需要速度时,这是在 C 语言中做到这一点的方法。如果你使用 GNU C 提供的 typeof() 扩展,它甚至可以使宏变得类型灵活。
7赞 John Nilsson 3/6/2009
犯 错。。。为什么要使用一个不能自己做内联的编译器?尽可能使用函数,必要时使用宏。函数是类型安全的,更容易理解。这个宏会用“swap(a++,b++)”做正确的事情吗?,函数会吗?
1赞 Joey Adams 12/18/2010
如果你使用的是像样的编译器,你可以使用 or 使它更通用。此外,一般来说,您应该添加括号以避免优先级问题(例如)。typeof(a)decltype(a)#define foo(a, b) bar(a, b, (a) + (b))
1赞 Petter 9/6/2012
这是一个可怕的解决方案。对于浮点数,它将默默地失败。它也缺少括号。
1赞 Peter Cordes 8/6/2014
@John:从另一个答案中复制我的评论:typeof 通常允许您编写避免多次评估其参数的宏。.或者你可以做,这样你就可以在值上使用它。希望编译器仍然可以优化将寄存器存储到内存中,以便它们有一个地址可以获取,用于交换寄存器中已经存在的两个局部变量。GNU libc 头文件在宏中大量使用技巧;那是我第一次看到它的地方。#define SWAP_BY_REF(a,b) do{ typeof(a) _a = (a); typeof(b) _b = (b); typeof(*_a) tmp=*_a; *_a=*_b; *_b=tmp;}while(0)_a=&a_a=(a)
11赞 Skizz 9/5/2008 #11

关于@Harry: 切勿将函数实现为宏,原因如下:

  1. 类型安全。没有。以下代码仅在编译时生成警告,但在运行时失败:

    float a=1.5f,b=4.2f;
    swap (a,b);
    

    模板化函数将始终具有正确的类型(为什么不将警告视为错误?

    编辑:由于 C 中没有模板,因此您需要为每种类型编写单独的交换或使用一些 hacky 内存访问。

  2. 这是一个文本替换。以下操作在运行时失败(这次没有编译器警告):

    int a=1,temp=3;
    swap (a,temp);
    
  3. 它不是一个函数。因此,它不能用作 qsort 之类的参数。

  4. 编译器很聪明。我的意思是真的很聪明。由非常聪明的人制作。它们可以对函数进行内联。即使在链接时(这更聪明)。不要忘记内联会增加代码大小。大代码意味着在获取指令时缓存未命中的机会更大,这意味着代码速度较慢。
  5. 副作用。宏有副作用!考虑:

    int &f1 ();
    int &f2 ();
    void func ()
    {
      swap (f1 (), f2 ());
    }
    

    在这里,f1 和 f2 将被调用两次。

    编辑:具有令人讨厌的副作用的C版本:

    int a[10], b[10], i=0, j=0;
    swap (a[i++], b[j++]);
    

宏:说不就行了!

编辑:这就是为什么我更喜欢以大写字母定义宏名称,以便它们在代码中脱颖而出,作为谨慎使用的警告。

编辑2:回答Leahn Novash的评论:

假设我们有一个非内联函数 f,它被编译器转换为字节序列,那么我们可以定义字节数,这样:

bytes = C(p) + C(f)

其中 C() 给出生成的字节数,C(f) 是函数的字节数,C(p) 是“内务”代码的字节,编译器添加到函数的前导码和后半段(创建和销毁函数的堆栈帧等)。现在,调用函数 f 需要 C(c) 字节。如果函数被调用 n 次,则总代码大小为:

size = C(p) + C(f) + n.C(c)

现在让我们内联函数。C(p) 是函数的“内务处理”,变为零,因为该函数可以使用调用方的堆栈帧。C(c) 也为零,因为现在没有调用操作码。但是,只要有调用,f 就会被复制。因此,现在的总代码大小为:

size = n.C(f)

现在,如果 C(f) 小于 C(c),则整体可执行文件大小将减小。但是,如果 C(f) 大于 C(c),则代码大小将会增加。如果 C(f) 和 C(c) 相似,那么您还需要考虑 C(p)。

那么,C(f) 和 C(c) 产生多少字节。好吧,最简单的 C++ 函数是 getter:

void GetValue () { return m_value; }

这可能会生成四字节指令:

mov eax,[ecx + offsetof (m_value)]

这是四个字节。调用构造为 5 个字节。因此,整体尺寸节省。如果函数更复杂,例如索引器(“return m_value [index];”)或计算(“return m_value_a + m_value_b;”),则代码会更大。

评论

4赞 Dan Lenski 10/1/2008
你的副作用代码是 C++,而不是 C(C 中没有引用)。C 程序员没有模板化函数......它可能具有一些类型安全性,但解析和以其他方式实现绝对是一场噩梦。C++ != C.它们具有不同类型和程度的抽象和约定。
4赞 Dan Lenski 10/1/2008 #12

除非你必须,否则我不会用指针来做。由于指针混叠的可能性,编译器无法很好地优化它们(尽管如果您可以保证指针指向非重叠位置,GCC 至少具有扩展来优化这一点)。

我根本不会用函数来做,因为这是一个非常简单的操作,而且函数调用开销很大。

最好的方法是使用宏,如果原始速度和优化的可能性是您需要的。在 GCC 中,您可以使用内置版本来制作适用于任何内置类型的灵活版本。typeof()

像这样的东西:

#define swap(a,b) \
  do { \
    typeof(a) temp; \
    temp = a; \
    a = b; \
    b = temp; \
  } while (0)

...    
{
  int a, b;
  swap(a, b);
  unsigned char x, y;
  swap(x, y);                 /* works with any type */
}

使用其他编译器时,或者如果需要严格遵守标准 C89/99,则必须为每种类型创建单独的宏。

一个好的编译器会在给定上下文的情况下尽可能积极地优化它,如果使用局部/全局变量作为参数进行调用。

评论

0赞 Johannes Schaub - litb 3/6/2009
我喜欢你的回答。这是我想到的第一件事。您可能希望为 C99 代码添加“register”的使用,这也告诉编译器它们没有别名(如果程序员知道参数不是同一个对象,则可以使用)
1赞 Dan Cristoloveanu 10/10/2008 #13

在我看来,像这样的本地优化应该只考虑与平台密切相关。如果您在 16 位 uC 编译器或以 x64 为目标的 gcc 上编译它,这将产生巨大的差异。

如果您有一个特定的目标,那么只需尝试这两种方法并查看生成的 asm 代码或使用这两种方法分析您的应用程序,看看哪种方法在您的平台上实际上更快。

4赞 4 revsTrevor Boyd Smith #14

所有最受好评的答案实际上都不是确定的“事实”......他们是投机的人!

您可以明确地知道哪些代码需要较少的汇编指令来执行,因为您可以查看编译器生成的输出程序集,并查看哪些代码在较少的汇编指令中执行!

这是我用标志“gcc -std=c99 -S -O3 lookingAtAsmOutput.c”编译的 c 代码:

#include <stdio.h>
#include <stdlib.h>

void swap_traditional(int * restrict a, int * restrict b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void swap_xor(int * restrict a, int * restrict b)
{
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

int main() {
    int a = 5;
    int b = 6;
    swap_traditional(&a,&b);
    swap_xor(&a,&b);
}

swap_traditional() 的 ASM 输出>>> 11 条<<<指令(不包括 “leave”、“ret”、“size”):

.globl swap_traditional
    .type   swap_traditional, @function
swap_traditional:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    movl    12(%ebp), %ecx
    pushl   %ebx
    movl    (%edx), %ebx
    movl    (%ecx), %eax
    movl    %ebx, (%ecx)
    movl    %eax, (%edx)
    popl    %ebx
    popl    %ebp
    ret
    .size   swap_traditional, .-swap_traditional
    .p2align 4,,15

swap_xor() 的 ASM 输出采用>>> 11 条<<<指令,不包括 “leave” 和 “ret”:

.globl swap_xor
    .type   swap_xor, @function
swap_xor:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %ecx
    movl    12(%ebp), %edx
    movl    (%ecx), %eax
    xorl    (%edx), %eax
    movl    %eax, (%ecx)
    xorl    (%edx), %eax
    xorl    %eax, (%ecx)
    movl    %eax, (%edx)
    popl    %ebp
    ret
    .size   swap_xor, .-swap_xor
    .p2align 4,,15

程序集输出摘要:
swap_traditional() 需要 11 条指令 swap_xor() 需要 11 条指令

结论:
两种方法都使用相同数量的指令来执行,因此在此硬件平台上的速度大致相同。

经验教训:
当你有小代码片段时,查看 asm 输出有助于快速迭代你的代码并提出最快(即最少指令)的代码。而且,即使您不必为每次代码更改运行程序,也可以节省时间。只需在最后使用探查器运行代码更改,即可显示代码更改速度更快。

对于需要速度的繁重 DSP 代码,我经常使用这种方法。

评论

1赞 Adam Rosenfield 3/6/2009
看起来您没有启用优化 - 局部变量在每个函数中被加载/存储多次。此外,在现代处理器中,您不能轻易计算周期数,因为任何触及内存的东西都需要可变的周期数,具体取决于缓存是否命中。
1赞 Trevor Boyd Smith 3/6/2009
我确实使用“-o3”启用了优化,我什至使用了“restrict”关键字来确保编译器将进行优化。我还错过了什么?--- 假设我计算的周期数不是绝对计数。但我至少认为这将是一个相对计数?所以传统。方法还是赢了?
4赞 Adam Rosenfield 3/7/2009
-o3 表示“将输出文件命名为 3”。您需要 -O3(大写字母 O)。
5赞 bendin 3/10/2009
在流水线超标量(即连续)CPU 上,你不能只计算汇编代码中的指令数并将其称为“周期”。
1赞 alecov 2/4/2017
“这两种方法都使用相同数量的指令来执行,因此在这个硬件平台上的速度大致相同。因此?你的推理是完全有缺陷的。显然,速度不仅仅是指令计数。
-1赞 jheriko 3/23/2009 #15

如果您的编译器支持内联汇编程序,并且您的目标是 32 位 x86,那么 XCHG 指令可能是执行此操作的最佳方法......如果你真的那么在乎性能。

下面是适用于 MSVC++ 的方法:

#include <stdio.h>

#define exchange(a,b)   __asm mov eax, a \
                        __asm xchg eax, b \
                        __asm mov a, eax               

int main(int arg, char** argv)
{
    int a = 1, b = 2;
    printf("%d %d --> ", a, b);
    exchange(a,b)
    printf("%d %d\r\n", a, b);
    return 0;
}

评论

2赞 Peter Cordes 8/5/2014
内联 ASM 使编译器更难优化。如果 xchg 更快,编译器就会使用它。它不是,因为它有一个隐式锁前缀。(非常慢)
1赞 jheriko 7/1/2015
右。我不知道这一点......谢谢你启发我:)
-3赞 Theofanis Pantelides 6/18/2009 #16
void swap(int* a, int* b)
{
    *a = (*b - *a) + (*b = *a);
}

我的 C 有点生疏,所以我希望我得到正确的 * :)

-4赞 Vadakkumpadath 10/8/2009 #17

另一种美丽的方式。

#define Swap( a, b ) (a)^=(b)^=(a)^=(b)

优势

无需函数调用,方便使用。

缺点:

当两个输入都是相同的变量时,此操作将失败。它只能用于整数变量。

8赞 SugarD 2/21/2013 #18

从来不理解对宏的憎恨。如果使用得当,它们可以使代码更加紧凑和可读。我相信大多数程序员都知道应该谨慎使用宏,重要的是明确特定调用是宏而不是函数调用(全部大写)。如果是问题的持续根源,也许编程不适合您。SWAP(a++, b++);

诚然,xor 技巧在你看到它的前 5000 次很巧妙,但它真正所做的只是以牺牲可靠性为代价来节省一个临时的。查看上面生成的程序集,它保存了一个寄存器,但创建了依赖项。另外,我不推荐 xchg,因为它有一个隐含的锁前缀。

最终,我们都来到了同一个地方,在浪费了无数个小时之后,我们最聪明的代码导致了非生产性的优化和调试 - 保持简单。

#define SWAP(type, a, b) \
    do { type t=(a);(a)=(b);(b)=t; } while (0)

void swap(size_t esize, void* a, void* b)
{
    char* x = (char*) a;
    char* y = (char*) b;
    char* z = x + esize;

    for ( ; x < z; x++, y++ )
        SWAP(char, *x, *y);
}

评论

0赞 SugarD 2/26/2013
被截断了?也许舒格理查德在大侦探的黄昏中会更合适。
2赞 Sulthan 12/6/2013
这比函数好吗?
1赞 Peter Cordes 8/6/2014
typeof 通常允许您编写避免多次计算其参数的宏。.或者你可以执行 _a=&a,这样你就可以在值而不是指针上使用它。希望编译器仍然可以优化将寄存器存储到内存中,以便它们有一个地址可以获取,用于交换寄存器中已经存在的两个局部变量。GNU libc 头文件在宏中大量使用技巧;那是我第一次看到它的地方。#define SWAP_BY_REF(a,b) do{ typeof(a) _a = (a); typeof(b) _b = (b); typeof(*_a) tmp=*_a; *_a=*_b; *_b=tmp;}while(0)typeof(a) _a=(a)
0赞 yyny 11/13/2017
@PeterCordes 是特定于 GCC 的扩展。typeof
5赞 herohuyongtao 1/15/2014 #19

对于现代 CPU 架构,方法 1 会比方法 2 更快,也具有更高的可读性。

在现代 CPU 架构上,XOR 技术比使用临时变量进行交换要慢得多。原因之一是现代 CPU 努力通过指令管道并行执行指令。在异或技术中,每个操作的输入都取决于前一个操作的结果,因此它们必须严格按顺序执行。如果效率非常受关注,建议在目标架构上测试异或技术和临时变量交换的速度。查看此处了解更多信息。


编辑:方法 2 是一种就地交换的方法(即不使用额外的变量)。为了完成这个问题,我将使用 添加另一个就地交换。+/-

void swap(int* a, int* b)
{
    if (a != b) // important to handle a/b share the same reference
    {
        *a = *a+*b;
        *b = *a-*b;
        *a = *a-*b;
    }
}

评论

0赞 CrepeGoat 7/11/2016
实际上,对于 +/- 就地交换,首先确保 .假设我们在声明 const 变量 之前添加一行,使得 和 为真。然后: -> 等于 ; -> 等于 ,即 只是 ; -> 等于 ,即 只是 ;=> , -> OKa!=bconst int C = *aC == *aC == *b*a = *a + *b*aC+C*b = *a - *b*bC+C-CC*a = *a - *b*aC+C-CC*a == C*b == C
0赞 herohuyongtao 7/12/2016
@Shillard 跳过不必要的交换可能并不重要,但很有用。:P
0赞 CrepeGoat 9/29/2016
我不建议在代码中添加逻辑分支,因为它没有添加任何功能。(当然,如果您已经对其进行了速度测试以对您的特定情况有利,即 70+% 的时间或其他什么,那么这是有道理的......但由于这是一个一般的答案,因此没有特殊情况,因此最好省略逻辑分支。此外,代码中的“处理 a/b 共享相同引用很重要”注释也不准确。a==b
-3赞 Ashwin Balaji Kuppuraj 11/9/2015 #20

下面的代码段将执行相同的操作。此代码段是优化的编程方式,因为它不使用任何第三个变量。

  x = x ^ y;
  y = x ^ y;
  x = x ^ y;

评论

4赞 ghybs 11/9/2015
欢迎来到 SO!请注意,这个问题可以追溯到 2008 年(7 年前),您的答案已经是该问题的一部分。OP 实际上是在询问速度性能,而不是内存。
2赞 Marcin Snieg 8/23/2017 #21

x=x+y-(y=x);

float x; cout << "X:"; cin >> x;
float y; cout << "Y:" ; cin >> y;

cout << "---------------------" << endl;
cout << "X=" << x << ", Y=" << y << endl;
x=x+y-(y=x);
cout << "X=" << x << ", Y=" << y << endl;

评论

0赞 Andrew Henle 3/18/2020
这忽略了整数溢出的可能性以及由此产生的未定义行为。