是否有必要在不使用第三个变量的情况下交换两个变量?

时间:2014-10-09 07:42:07

标签: performance algorithm assembly language-agnostic swap

我知道不要使用它们,但有些技术可以在不使用第三个变量的情况下交换两个变量,例如

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

x = x + y
y = x - y
x = x - y 

在课堂上,教授提到这些在20年前流行,当时内存非常有限,至今仍在高性能应用程序中使用。这是真的?我对使用这种技术毫无意义的理解是:

  1. 使用第三个变量永远不会成为瓶颈。
  2. 无论如何,优化器都会这样做。
  3. 那么有没有时间不与第三个变量交换?是不是更快?

    相互比较,使用XOR的方法与使用+/-更快的方法相比?大多数架构都有一个加/减和XOR的单位,所以不会意味着它们的速度都相同吗?或者仅仅因为CPU有一个操作单元并不意味着它们具有相同的速度?

6 个答案:

答案 0 :(得分:21)

对于编写普通洗衣机固件的程序员来说,这些技术仍然很重要。许多类型的硬件仍然在Z80 CPU或类似产品上运行,通常只有不超过4K的内存。在那个场景之外,正如你所说,知道这些算法“欺骗”就像没有实际用途一样好。

(尽管如此,我确实想说,那些记住并了解这类内容的程序员经常会成为更好的程序员,甚至是“常规”应用程序,而不是那些不会打扰他们的“同行”。正是因为后者往往采取“记忆力足够大”的态度。)

答案 1 :(得分:14)

根本没有意义。这是为了表现出聪明才智。考虑到它在许多情况下(浮点,指针,结构)不起作用,是不可读的,并且使用三个相关操作,这比仅交换值慢得多,它绝对没有意义并且演示了未能真正 聪明。

你是对的,如果它更快,那么优化编译器会在交换两个数字时检测到模式,并替换它。这很容易做到。但编译器实际上会注意到你交换两个变量并且根本不会产生代码,但之后开始使用不同的变量。例如,如果你交换x和y,那么写一个+ = x; b + = y;编译器可能只是将其更改为+ = y; b + = x; 。另一方面,xor或加/减模式将无法被识别,因为它非常罕见并且不会得到改进。

答案 2 :(得分:9)

是的,尤其是汇编代码。

处理器只有有限数量的寄存器。当寄存器非常满时,这个技巧可以避免将寄存器溢出到另一个存储器位置(可能存在于未获取的高速缓存行中)。

我实际上使用3路xor来交换带有内存位置的寄存器,用于x86的高性能手动编码锁定例程的关键路径,其中寄存器压力很高,而且没有(锁定安全) !)放置温度的地方。 (在X86上,了解内存的XCHG指令与它相关的成本很高是有用的,因为它包含了自己的锁,其效果我不想要。鉴于x86具有LOCK前缀操作码,这真的很有用不必要的,但历史错误就是这样。)

士气:每一个解决方案,无论站在孤立状态下看起来多么丑陋,都可能有一些用途。了解它们很有意义;如果不合适,你总是不能使用它们。在它们有用的地方,它们可以非常有效。

答案 3 :(得分:7)

这样的结构对于PIC系列微控制器的许多成员都很有用,这些成员要求几乎所有操作都通过一个累加器("工作寄存器")[注意虽然这有时会成为障碍事实上,每条指令只需编码一个寄存器地址和一个目标位而不是两个寄存器地址,这使PIC可以拥有比其他微控制器大得多的工作集。

如果工作寄存器保存了一个值,并且需要将其内容与RAM的内容交换,则替代:

xorwf other,w  ; w=(w ^ other)
xorwf other,f  ; other=(w ^ other)
xorwf other,w  ; w=(w ^ other)

将是

movwf temp1     ; temp1 = w
movf  other,w   ; w = other
movwf temp2     ; temp2 = w
movf  temp1,w   ; w = temp1 [old w]
movwf other     ; other = w
movf  temp2,w   ; w = temp2 [old other]

三条指令,无额外存储,六条指令和两条额外寄存器。

顺便提一下,另一个技巧可能有助于在一个人想要另一个寄存器保持其现值或W的最大值时,并且之后不再需要W的值

subwf other,w    ; w = other-w
btfss STATUS,C   ; Skip next instruction if carry set (other >= W)
 subwf other,f   ; other = other-w [i.e. other-(other-oldW), i.e. old W]

我不确定有多少其他处理器有减法指令但没有非破坏性比较,但在这样的处理器上,技巧可能是一个很好的知识。

答案 4 :(得分:5)

如果你想在内存或两个整个寄存器中交换两个完整的单词,这些技巧不太可能有用。如果你没有空闲寄存器(或者只有一个空闲寄存器用于存储器到存储器交换)并且没有可用的“交换”指令(比如在x86中交换两个SSE寄存器)或“交换”时,你仍然可以利用它们。指令过于昂贵(如x86中的寄存​​器内存xchg),无法避免交换或降低寄存器压力。

但如果您的变量是单个字中的两个位域,则修改3-XOR方法可能是一个好主意:

y = (x ^ (x >> d)) & mask
x = x ^ y ^ (y << d)

这段摘录来自Knuth的“计算机编程艺术”第一卷。 4A。秒。 7.1.3。这里y只是一个临时变量。要交换的两个位域都在xmask用于选择位域,d是位域之间的距离。

你也可以在硬度证明中使用这样的技巧(以保持平面性)。例如,请参阅this slide中的交叉小工具(第7页)。这是教授"Algorithmic Lower Bounds"最近的讲座。 Erik Demaine。

答案 5 :(得分:1)

当然知道它仍然有用。有什么替代方案?

c = a
a = b
b = c

三个资源的三个操作,而不是两个资源的三个操作?

当然,指令集可能有交换,但只有在1)编写汇编时才会发挥作用或2)优化器将其视为交换,然后对该指令进行编码。或者您可以进行内联汇编,但这不是可移植的,并且维护起来很痛苦,如果您调用了asm函数,那么编译器必须为调用设置更多资源和指令。虽然可以这样做,但除非语言具有交换操作,否则您不太可能实际利用指令集功能。

现在普通程序员现在不需要知道这一点,人们会抨击这种过早的优化,除非你知道这个技巧并经常使用它,如果代码没有记录,那么它并不明显所以这是糟糕的编程,因为它是不可读和不可维护的。

它仍然是一种价值编程教育和练习,例如让一个人发明一个测试来证明它实际上交换所有位模式的组合。就像做xor reg一样,在x86上注册到一个寄存器,它对于高度优化的代码有一个很小但实际的性能提升。