【问题标题】:fastest way to negate a number否定一个数字的最快方法
【发布时间】:2013-02-27 11:54:00
【问题描述】:

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

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

int a = 10;
a = -a;

但后来,我想,我用它来做到这一点,使用命令 shift 和 pointers ... 那真的有可能改变一个值的符号,使用命令移位运算符和内存?

【问题讨论】:

  • 编译器可能会优化这些简单的东西。自己试试吧。
  • 同意泽塔。寻找最易读的东西
  • 不确定你想用“指针”做什么以及为什么你认为使用它们会比a = -a; 更快。其实我不明白为什么你认为编译器在编译a = -a;时不一定会使用最高效的实现。
  • 除非汇编器放在inline 并且绝对不会影响代码生成,否则它不可能产生比编译器更好的代码...
  • 编译语言规则1:不要对编译器撒谎;它稍后会回来咬你。如果您发现编译器工作不佳,请向开发人员提交错误报告。优化您的算法,而不是您的基本操作。

标签: c++ visual-c++ x86 micro-optimization visual-c++-2012


【解决方案1】:

使用可读的东西,例如

a *= -1;

a = -a;

剩下的交给优化器。

【讨论】:

    【解决方案2】:

    禁用优化后,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 的性能,您可以相应地优化您的源代码。通常,您只关心优化构建中的性能。

    【讨论】:

    • gcc 没有优化! gcc -c optimum.c -S -o optimum.s
    • @MrLister 任何体面的自尊编译器都会产生相同(相似)的代码。
    • 重点是:不要猜测,不要假设,不要读心:)这个问题一般是关于 C 或 C++ 的。但实际上,除非您对特定 CPU 有深入的了解,否则尝试手动优化根本没有任何意义。 99% 的程序员并不比为特定 CPU 编写编译器端口的人更了解这些事情。
    • @Aniket 您应该能够在编辑历史记录中看到他的编辑。分别是movl $10, 12(%esp) // i = 10negl 12(%esp) // i = -i。 @GrijeshChauhan 通过 cmets 建议对海报进行此类更改而不是自己编辑帖子被认为更好,因为您将更改内容的含义。
    • @Aniket:MSVS 2012 编译优化了吗?也许您可以指出这一点——也许 /Odtp 意味着,但这并不明显!最好只显示列表中的差异。
    【解决方案3】:

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

    • 您应该忘记速度,选择您认为最易读的成语。
    • 几乎所有编译器(启用优化)都会为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;
    

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


    1Peter Cordes的观察:几乎所有的编译器都明白a = -aa *= -1是完全一样的,并且会发出他们决定的任何asm无论您如何编写它,都将在目标 CPU 上最有效。 (例如,Godbolt compiler explorer 用于 x86 gcc/MSVC/clang 和 ARM gcc。)但尽管 MSVS 2012(仅在调试模式下)对每个指令使用一条指令,但 = -a 和3 代表最近的 Intel CPU 上的 *= -1,使用实际的 imul 指令。

    【讨论】:

      【解决方案4】:

      也是 0 - n

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

          你可以试试

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

          但您不必担心,因为编译器会以最好的方式做到这一点。

          【讨论】:

          • 这正好说明了为什么这种优化是一个坏主意。正如所写,它既是更多的操作,又是更少的可读性。一个体面的编译器将帮助您解决第一个问题,但不能帮助您解决第二个问题。
          • @rici 且不可移植,因为它依赖于二进制补码。
          • @rici:它在 C 抽象机器中有更多的操作,但它仍然使用 gcc/clang/MSVC godbolt.org/g/C3F3Qx 编译为 neg(在提供这些的 x86 和 ARM 等 2 的补码机器上一条指令中的操作)。如果您使用unsigned 执行此操作(并且仅在位操作后转换回int),它具有有趣的优势,即在INT_MIN 上不是UB,而-aa * -1 将被签名溢出。这是 only 的优势。它的可读性很差,而且不便携。
          • @peter:虽然我在四年半前写了那条评论,但我认为可以坚持下去。我确实说过一个像样的编译器会优化操作,所以 gcc、clang 等这样做的事实与我写的完全一致。为什么你声称-aunsigned 值上的 UB?
          • @rici:我声称对于int a = INT_MIN,它是UB 做-a,而不是做(int)(~(unsigned)a + 1)。当然,现在我想起来了,你可以只做(int)-(unsigned)a 来获得相同的结果(两者的补码/无符号否定,即使在一个补码或符号/幅度 C++ 实现上)。所以nvm,这个优势为零。 (回复:效率:我只是说这不是更糟。绝对不是更好!正如你所说,它与-*= -1编译相同。)
          【解决方案7】:

          使用高级语言的解决方案

          此类问题在面试和竞争激烈的编程世界中很受欢迎。

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

          为此:

          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处理进位操作

          【讨论】:

            猜你喜欢
            • 2011-02-24
            • 2016-05-02
            • 2016-08-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-04
            • 1970-01-01
            相关资源
            最近更新 更多