以有效的方式乘以7

A.M*_*M.M 9 c

我最近遇到了以下面试问题:

如何以高效优化的方式将数字乘以7?

我知道我可以乘以8(或左移三位)然后减去原始值:

num = (num << 3) - num;
Run Code Online (Sandbox Code Playgroud)

但还有其他解决方案.

pax*_*blo 16

对于有限范围,您可以使用查找表:

static unsigned int mult7[] = {0, 7, 14, 21, ...};
unsigned int three = 3;
unsigned int twenty_one = mult7[three];
Run Code Online (Sandbox Code Playgroud)

这可能听起来愚蠢(很可能是这种特定的情况下),但它往往是很方便的事情,那里是一个真正以计算成本.我不确定将七个计数乘以其中一个案例.

首先,乘以x7(或向x左移三位然后减去x)是一种可以完全在CPU内部完成的操作.使用表查找,您可能会看到乘以4(向左移动两位),然后是添加以获取正确的地址,但是您必须访问内存才能进行实际查找 - 即使使用缓存和所有其他当前CPU有能力的奇妙技巧,这可能会减慢速度.

您的编译器也很有可能已经知道有关如何快速乘法的所有技巧.如果你的七是一个常数(或const int等价),编译器可能已经选择了最快的方式,编译器编写者很有可能比凡人更了解这类东西:-)(a)

但在计算成本的情况下比较高的,一旦计算值,并在你的代码中嵌入它们作为一个查找表是标准的优化策略之一(权衡时间换空间).


(a)检查以下代码:

#include <stdio.h>

static int mult7 (int num) {
    return num * 7;
}

int main (int argc, char *argv[]) {
    printf ("%d\n", mult7 (atoi (argv[1])));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

通过正常编译gcc,mult7出现左移三和减去技巧:

_mult7:
    pushl   %ebp             ; stack frame setup.
    movl    %esp, %ebp
    movl    8(%ebp), %edx    ; get value to edx
    movl    %edx, %eax       ;    and eax.
    sall    $3, %eax         ; eax <- eax * 8.
    subl    %edx, %eax       ; eax <- eax - edx.
    popl    %ebp             ; stack frame teardown and return.
    ret
Run Code Online (Sandbox Code Playgroud)

-O3(我喜欢称之为疯狂的优化级别),整个事情内联到main:

call    _atoi
movl    $LC0, (%esp)

leal    0(,%eax,8), %edx     ; these two are the relevant instructions.
subl    %eax, %edx

movl    %edx, 4(%esp)
call    _printf
Run Code Online (Sandbox Code Playgroud)

请注意,只有函数的静态特性才能实现此内联操作 - 如果链接器可以看到它,则必须将其维护为单独的函数,以防另一个目标文件需要调用它.

如果你取下static它,它确实保持非内联所有堆栈框架设置和拆卸,但它至少仍然使用下面提到的(可能)更有效的技巧.你可以摆脱堆栈框架代码,gcc如果你使用-fomit-frame-pointer提供这不会对代码产生负面影响,但这开始深入研究黑暗的一面:-)

这招是使用LEA指令设置edxeax * 8再减去eax从.与sall/subl正常优化相同的理论,只是略有不同的机制.

最重要的是,相信你的编译器.如果要乘以num7,请使用以下命令:

num *= 7;
Run Code Online (Sandbox Code Playgroud)

很可能无论您从这种尝试的微优化中获得什么改进,您都可以通过查看宏观级别(算法和数据结构选择等)获得更好的改进.


Kei*_*son 15

要以有效的方式获得7的倍数:

7
Run Code Online (Sandbox Code Playgroud)

7是7的倍数.这回答了你问的问题,但我确定它没有回答你的问题.

编辑:以上是基于问题的原始标题,我刚刚更正.

7高效,只写,例如:

x * 7
Run Code Online (Sandbox Code Playgroud)

并通过优化调用您的编译器.让编译器弄清楚单个MUL指令或类似的东西对于当前机器(x<<3) - x是否更有效.

这里还有另一个隐含的问题:面试官在寻找什么答案?我希望 "让编译器担心它"将是一个可以接受的答案. (x<<3) - x可能是最明显的微优化 - 但如果x<<3溢出它会产生错误的答案,并且取决于系统它可能比MUL指令慢.

(如果我是面试官,对问题的解释和理解比任何具体答案都要好得多.)

编辑

进一步思考,如果您比编译器更了解可能的值,那么此处讨论的微优化类型可能会有用x.如果你知道,由于程序逻辑的性质,它x总是在0..10的范围内,那么查找表可能比乘法运算更快.或者,如果你知道x99%的时间都在该范围内,那么回溯到实际乘法的查找表可能就是这样.

但是,如果你的程序流程的编译器的分析并没有允许它证明x始终是在这个范围内,那么就不能执行这种优化.

但这种情况非常罕见.当您的代码在一个x可能为11 的新环境中运行时(可能它在具有更大显示器的设备上运行),kaboom.而且性能改善很可能首先并不显着.

有时微观优化是合适的,但在开发和测试时间方面存在大量成本.只有在实际测量表明它值得的时候才这样做.


Aus*_*oke 6

我这样做的方式就像

num = (num << 3) - num;
Run Code Online (Sandbox Code Playgroud)

即.2 ^ 3 = 8,然后减去乘以的数字得到7的倍数.

我刚用gcc编译了下面的代码:

int mul(int num)
{
   return num * 7;
}
Run Code Online (Sandbox Code Playgroud)

这是它编译为的gdb转储:

Dump of assembler code for function mul:
   0x00000000004004c4 <+0>:    push   rbp
   0x00000000004004c5 <+1>:    mov    rbp,rsp
   0x00000000004004c8 <+4>:    mov    DWORD PTR [rbp-0x4],0xa
   0x00000000004004cf <+11>:   mov    edx,DWORD PTR [rbp-0x4]
   0x00000000004004d2 <+14>:   mov    eax,edx
   0x00000000004004d4 <+16>:   shl    eax,0x3
   0x00000000004004d7 <+19>:   sub    eax,edx
   0x00000000004004d9 <+21>:   mov    DWORD PTR [rbp-0x4],eax
   0x00000000004004dc <+24>:   pop    rbp
   0x00000000004004dd <+25>:   ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

所以看起来我的机器左移3次然后减去乘以的数字是gcc认为可能是最佳的.

编辑:结果优化级别至少为1(-O1),gcc使用lea技巧:

Dump of assembler code for function mul:
   0x00000000004004e0 <+0>: lea    eax,[rdi*8+0x0]
   0x00000000004004e7 <+7>: sub    eax,edi
   0x00000000004004e9 <+9>: ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)