【问题标题】:Variable swap with and without auxiliary variable - which is faster?有和没有辅助变量的变量交换 - 哪个更快?
【发布时间】:2011-12-19 21:09:33
【问题描述】:

我猜你们都听说过“交换问题”; SO充满了关于它的问题。 不使用第三个变量的交换版本通常被认为更快,因为你少了一个变量。我想知道幕后发生了什么,并编写了以下两个程序:

int main () {
    int a = 9;
    int b = 5;
    int swap;

    swap = a;
    a = b;
    b = swap;

    return 0;
}

以及没有第三个变量的版本:

int main () {
    int a = 9;
    int b = 5;

    a ^= b;
    b ^= a;
    a ^= b;

    return 0;
}

我使用 clang 生成了汇编代码,并为第一个版本(使用第三个变量)得到了这个:

...
Ltmp0:
    movq   %rsp, %rbp
Ltmp1:
    movl   $0, %eax
    movl   $0, -4(%rbp)
    movl   $9, -8(%rbp)
    movl   $5, -12(%rbp)
    movl   -8(%rbp), %ecx
    movl   %ecx, -16(%rbp)
    movl   -12(%rbp), %ecx
    movl   %ecx, -8(%rbp)
    movl   -16(%rbp), %ecx
    movl   %ecx, -12(%rbp)
    popq   %rbp
    ret
Leh_func_end0:
...

第二个版本(不使用第三个变量):

...
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl   $0, %eax
    movl   $0, -4(%rbp)
    movl   $9, -8(%rbp)
    movl   $5, -12(%rbp)
    movl   -12(%rbp), %ecx
    movl   -8(%rbp), %edx
    xorl   %ecx, %edx
    movl   %edx, -8(%rbp)
    movl   -8(%rbp), %ecx
    movl   -12(%rbp), %edx
    xorl   %ecx, %edx
    movl   %edx, -12(%rbp)
    movl   -12(%rbp), %ecx
    movl   -8(%rbp), %edx
    xorl   %ecx, %edx
    movl   %edx, -8(%rbp)
    popq   %rbp
    ret
Leh_func_end0:
...

第二个更长,但我对汇编代码了解不多,所以我不知道这是否意味着它更慢,所以我想听听更了解它的人的意见。

以上哪个版本的变量交换速度更快,占用的内存更少?

【问题讨论】:

  • 要找出哪个更快,为什么不对其进行基准测试?
  • 我不知道如何测量内存使用量,而且我也对它背后的原因感兴趣。
  • 看起来您编译时没有启用优化。那个程序集里​​有很多绒毛。
  • XOR 技巧可能在很久以前就已经更快了,但在现代 CPU 上却没有。只需使用一个临时变量并启用编译器优化。
  • 试图判断编译器生成代码的速度而忽略优化是没有意义的

标签: c assembly swap


【解决方案1】:

查看一些优化的程序集。来自

void swap_temp(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;
}

gcc -O3 -std=c99 -S -o swapping.s swapping.c 产生

    .file   "swapping.c"
.text
.p2align 4,,15
.globl swap_temp
.type   swap_temp, @function
swap_temp:
.LFB0:
.cfi_startproc
movl    (%rdi), %eax
movl    (%rsi), %edx
movl    %edx, (%rdi)
movl    %eax, (%rsi)
ret
.cfi_endproc
.LFE0:
.size   swap_temp, .-swap_temp
.p2align 4,,15
.globl swap_xor
.type   swap_xor, @function
swap_xor:
.LFB1:
.cfi_startproc
movl    (%rsi), %edx
movl    (%rdi), %eax
xorl    %edx, %eax
xorl    %eax, %edx
xorl    %edx, %eax
movl    %edx, (%rsi)
movl    %eax, (%rdi)
ret
.cfi_endproc
.LFE1:
.size   swap_xor, .-swap_xor
.ident  "GCC: (SUSE Linux) 4.5.1 20101208 [gcc-4_5-branch revision 167585]"
.section    .comment.SUSE.OPTs,"MS",@progbits,1
.string "Ospwg"
.section    .note.GNU-stack,"",@progbits

在我看来,swap_temp 看起来非常高效。

【讨论】:

  • 不错,感谢优化!这是尽可能快/短吗?顺便说一句,如果我交换指针而不是变量会有什么不同吗?
  • 我敢说swap_temp 是最优的。对于swap_xor,如果没有restrict 限定符,gcc 会少生成一条指令,它变成三个movl a, b; xorl c, d,其中在每个操作中,一个参数是一个寄存器(%eax,总是)和一个指针解引用(@ 987654330@ 或 (%rdi))。根据我的测量,它比较慢(但如果函数在调用站点可见,内联可以消除差异)。关于交换变量和交换指针之间的区别,交换变量永远无法隐藏,因此优化通常可以完全消除它。
【解决方案2】:

XOR 交换技巧的问题在于它是严格顺序的。它看起来似乎很快,但实际上并非如此。有一条名为 XCHG 的指令可以交换两个寄存器,但由于其原子性质,这也可能比简单地使用 3 MOVs 慢。使用 temp 的常用技术是一个很好的选择;)

【讨论】:

  • -1 xchg reg1, reg2 没有同步问题。同步问题仅出现在带有内存操作数的 xchg
  • @Johan 哇,好的,很高兴知道,谢谢! :)(我确定了答案)
  • 您编辑了答案,但仍然不正确。 XCHG reg, reg 有原子性问题,因此它不低于 3 MOVs。根据处理器的不同,XCHG 可能(也可能不会)分解为多个微操作。只有XCHG reg,[reg](与内存位置交换一个reg)很慢,因为它附加了一个隐含的LOCK前缀。 LOCK 前缀会减慢它的速度。
【解决方案3】:

要了解成本,假设每个命令都有要执行的成本,而且间接寻址也有自己的成本。

movl   -12(%rbp), %ecx

这一行需要一个时间单位来访问 ecx 寄存器中的值, 一个时间单位用于访问 rbp,另一个时间单位用于应用偏移量 (-12) 和更多时间 单位(假设任意 3)用于将值从存储在 ecx 中的地址移动到 从 -12(%rbp) 指示的地址。

如果你计算每一行和所有行中的所有操作,第二种方法肯定比第一种方法更昂贵。

【讨论】:

  • 在这种情况下是正确的,但不是普遍的,因为它忽略了流水线机会。
  • 同意,但是你必须知道如何优化你的代码以优化流水线和最小化分支。我认为我们的朋友更容易从减少不必要的引用和过多的命令开始,然后再使用更高级的技术。
猜你喜欢
  • 2014-04-26
  • 1970-01-01
  • 2011-12-07
  • 1970-01-01
  • 2011-08-28
  • 1970-01-01
  • 2014-12-04
  • 2012-11-22
  • 1970-01-01
相关资源
最近更新 更多