43

我今天早上在这里想,将多个正面反转为负面以及从负面反转为正面的最快方法是什么,当然,最简单的方法可能是:

int a = 10;
a = a*(-1);

或者

int a = 10;
a = -a;

但后来,我想,我会这样做,使用命令移位和指针......这真的有可能改变一个值的符号,使用命令移位运算符和内存?

4

7 回答 7

50

使用可读的东西,例如

a *= -1;

或者

a = -a;

剩下的留给优化器。

于 2013-02-27T11:56:08.743 回答
28

在禁用优化的情况下,gcc for x86 将第一个编译成这个 asm:

    .file   "optimum.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main               # MinGW library init function
    movl    $10, 12(%esp) ;i = 10
    negl    12(%esp)      ;i = -i
    movl    $0, %eax
    leave
    ret

禁用优化后,第二个会产生:

    .file   "optimum.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    movl    $10, 12(%esp)   ;i = 10
    negl    12(%esp)        ;i = -i
    movl    $0, %eax
    leave
    ret

一样的输出!生成的汇编代码没有区别。

-------------------------- 编辑,他使用 VC++2012 的 OP ANSWERS,INTEL ARCH----------- --------

编译使用cl optimum.c /Fa optimum.asm(优化禁用)

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  _main
; Function compile flags: /Odtp
_TEXT   SEGMENT
_a$ = -4                        ; size = 4
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_main   PROC
; File c:\users\dell\downloads\tth\tth\tth\optimum.c
; Line 4
    push    ebp
    mov ebp, esp
    push    ecx
; Line 5
    mov DWORD PTR _a$[ebp], 10          ; 0000000aH
; Line 6
    mov eax, DWORD PTR _a$[ebp]
    neg eax ;1 machine cycle!
    mov DWORD PTR _a$[ebp], eax
; Line 7
    xor eax, eax
; Line 8
    mov esp, ebp
    pop ebp
    ret 0
_main   ENDP
_TEXT   ENDS
END

使用第二种方法 ( a = a * -1),优化禁用 MSVC:

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  _main
; Function compile flags: /Odtp
_TEXT   SEGMENT
_a$ = -4                        ; size = 4
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_main   PROC
; File c:\users\dell\downloads\tth\tth\tth\optimum.c
; Line 4
    push    ebp
    mov ebp, esp
    push    ecx
; Line 5
    mov DWORD PTR _a$[ebp], 10          ; 0000000aH
; Line 6
    mov eax, DWORD PTR _a$[ebp]
    imul    eax, -1 ;1 instruction, 3 machine/cycles :|
    mov DWORD PTR _a$[ebp], eax
; Line 7
    xor eax, eax
; Line 8
    mov esp, ebp
    pop ebp
    ret 0
_main   ENDP
_TEXT   ENDS
END

因此,如果您关心 MSVC 下调试模式 asm 的性能,您可以相应地优化您的源代码。通常你只关心优化构建的性能。

于 2013-02-27T12:03:00.447 回答
11

其他答案正确地表明可读性更重要:

  • 您应该忘记速度并选择您认为最易读的成语。
  • 几乎所有编译器(启用了优化)都会为类似的任何东西生成等效的最佳代码(可能是一条指令)a = -aa *= -1等等。1
  • 任何让它更快的尝试都会使它的可读性大大降低,并且很容易让它变慢。
  • 如果您需要优化,您应该从分析生成的代码和性能开始。


然而,这个习语 有一个实际的优势*= -1:你只需要在左边写一次,它只被评估一次——读者只需要读一次!当 LHS 较长、复杂或昂贵或可能有副作用时,这是相关的:

(valid ? a : b)[prime_after(i++)] *= -1;
*look_up (input) *= -1;  // Where look_up may have side-effects
parity[state][(unsigned int)getc(stdin)] *= -1;
variable_with_a_long_explanatory_name *= -1;

一旦一个人采用了一种习语,人们往往会在其他情况下坚持使用它。


1 Peter Cordes的观察:几乎所有编译器都理解这一点a = -a,并且a *= -1完全相同,并且无论您如何编写它,都会发出他们认为在目标 CPU 上最有效的任何 asm。(例如,用于 x86 gcc/MSVC/clang 和 ARM gcc 的Godbolt 编译器资源管理器 。)但尽管 MSVS 2012(仅在调试模式下)对每个指令使用一条指令,但在最近的 Intel CPU 上,它们需要 1 个周期= -a和 3个周期*= -1,使用实际imul操作说明。

于 2015-05-27T15:12:02.177 回答
5

也是 0 - n

Gcc 为所有四种情况发出“否定”指令:-n、0 - n、n * -1 和 ~n + 1

于 2013-02-28T17:57:16.200 回答
4

假设处理器至少有一定的能力并且具有sizeof(int) == sizeof(Cpu_register),那么“使这个数字为负”将是一条指令(通常称为neg)[嗯,可能也需要加载和存储值,但是如果您将变量用于其他任何事情,它可以在加载后保留,并且只能在以后存储......]

乘以-1很可能比 慢a = -a;,但大多数有能力的编译器应该能够使这两者等效。

所以,只要把代码写清楚,剩下的就自己处理好了。在大多数处理器中,取反一个数字并不是什么困难的操作。如果你使用了一些不寻常的处理器,那么看看编译器的输出,看看它做了什么。

于 2013-02-27T12:03:57.937 回答
1

使用高级语言的解决方案

像这样的问题在面试和竞争激烈的编程世界中很流行。

我来到这里研究更多不使用 - 或 + 运算符的否定数字解决方案。

为了这 :

  1. 使用 ~ 运算符补数
  2. 然后使用半加法器逻辑将步骤 1 中获得的数字加 1:
> int addNumbers(int x, int y)
>       {
>                    if(y==0)  return x; // carry is 0 
>                    return addNumbers(x^y,(x&y)<<1);
>         }

这里 x^y 执行位相加, x&y 处理进位操作

于 2017-11-09T03:19:28.840 回答
-1

你可以试试

int a = 10;
a= ~a+1;

但你不应该担心这一点,因为编译器以最好的方式使它成为可能。

于 2013-02-27T12:02:14.497 回答