5.7 汇编语言:汇编高效乘法运算

这篇具有很好参考价值的文章主要介绍了5.7 汇编语言:汇编高效乘法运算。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

乘法指令是一种在CPU中实现的基本算术操作,用于计算两个数的乘积。在汇编语言中,乘法指令通常是通过mul(无符号乘法)imul(有符号乘法)这两个指令实现的。由于乘法指令在执行时所消耗的时钟周期较多,所以编译器在优化代码时通常会尝试将乘法操作转换为更高效的加法、和移位操作。

  • 对于较小的数,编译器可能会选择将乘法操作直接转换为加法操作。例如,将表达式a * b转换为a + a + ... + a(b次相加)的形式。这种方式可以通过循环展开、代码向量化等技术来优化。

  • 对于较大的数,编译器可能会使用位移和移位操作来代替乘法。例如,将表达式a * b转换为a << n + a << m的形式,其中nm为符合条件的位数。这种方式可以通过位移指令的高效性来加速运算。

当以上方式均无法进行优化时,编译器才会使用mul/imul指令来执行乘法操作。这两条指令可以对无符号数和有符号数进行乘法运算,即便这两条指令会使用更多的时钟周期,但乘法指令的计算效率相对于其他指令DIV来说仍然较低,因此在编写高效代码时,应尽可能地避免使用乘法操作,并结合使用上面提到的技巧进行优化。

7.1 使用IMUL指令完成乘法

要计算乘法在不考虑执行效率的情况下编译器通常会直接使用imul指令完成计算,imul指令在一些情况下可以比其他乘法指令(如mul指令)更快地执行乘法运算,但性能较低的原因主要是由于imul指令通常用于有符号数的乘法运算,并且在执行时需要处理符号位的扩展和溢出问题,这转换成了额外的指令和时钟周期的消耗。如果对于无符号整数或需要使用寄存器的低位或者高位结果的情况,使用imul指令可以提供一定的优势。

计算乘法时应遵循:

  • 如果乘数与被乘数都是8位 则把AL做乘数,结果放在AX
  • 如果乘数与被乘数都是16位 将把AX做乘数,结果放在EAX
  • 如果乘数与被乘数都是32位 将把EAX做乘数,结果放在EDX:EAX

乘法指令计算很简单,只需要累加乘数即可,如下所示则是一个简单的计算三个数相乘的汇编实现;

.data
    x DWORD ?
    y DWORD ?
    z DWORD ?
    szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
    main PROC
      mov dword ptr ds:[x],10
      mov dword ptr ds:[y],24
      mov dword ptr ds:[z],18
      
      ; 计算 x * y * z
      mov eax,dword ptr ds:[x]
      imul eax,dword ptr ds:[y]
      imul eax,dword ptr ds:[z]
      invoke crt_printf,addr szFmt,eax
    main ENDP
END main

7.2 使用LEA指令替换乘法

在实际编程中,我们可以使用LEA指令来替代乘法操作,从而提高代码的执行效率。但读者需要注意,在使用LEA计算乘法时必须要保证乘数是2的次幂,并且乘数的范围必须是2/4/8这三个区间才可使用该指令,我们使用汇编来实现计算eax*8+2其汇编指令如下。

  • 假设 eax=5 计算 eax * 8 + 2 的结果,拆分过程如下:
  • 1.计算 lea ebx,dword ptr ds:[eax * 8 + 2] 这就相当于计算 ebx = (eax * 8) +2直接可得到结果。

第一个案例比较简单,可直接使用一条lea指令即可完成计算过程,只要保证被乘数是2的次幂即可。

.data
  x DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    ; 针对乘法的lea指令优化
    mov dword ptr ds:[x],5
    
    mov eax,dword ptr ds:[x]               ; eax = x
    xor ebx,ebx                            ; ebx = 0
    lea ebx,dword ptr ds:[eax * 8 + 2]     ; ebx = eax * 8 + 2
    invoke crt_printf,addr szFmt,ebx
    
    invoke ExitProcess,0
  main ENDP
END main

7.3 使用LEA指令拆分计算

如果我们计算的乘法超出了2/4/8次幂范围,则需要对乘法进行拆分,拆分时也应遵循2的次幂原则,拆分后在分开来计算。

  • 假设 eax=3 计算 15 * eax 的结果,拆分过程如下:
  • 1.计算 lea edx,[eax * 4 + eax] 这就相当于计算 edx = (4 * eax) + eax = 5eax 其中的每个edx就相当于5个eax
  • 2.计算 lea edx,[edx * 2 + edx] 这就相当于计算 edx = (5 * eax) * 2 + (5 * eax)
  • 3.计算 (5eax * 2) = 10eax 接着计算 (5 * eax) = 5eax 最后得出 10eax + 5eax
  • 4.经过该过程可得出 eax * 15 = 45 最终计算3*15=45得到最终结果.

这个计算过程看似复杂,但如果将其转化为汇编指令那么只需要两条即可实现快速乘法运算。

.data
  x DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    ; 针对乘法的lea指令优化
    mov dword ptr ds:[x],3
    
    ; 如果使用lea计算乘法,则乘数必须是2/4/8
    mov eax,dword ptr ds:[x]               ; eax = 3
    lea edx,dword ptr ds:[eax * 4 + eax]   ; edx = 4eax + eax 得出 5eax,也就是说每一个edx就代表5个eax
    lea edx,dword ptr ds:[edx * 2 + edx]   ; edx = (5eax * 2) + 5eax 最终得出 15eax
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 15 计算后得出 45
    
    invoke ExitProcess,0
  main ENDP
END main

7.4 使用LEA指令递减计算

如果计算乘法时乘数非2的次幂,这种情况下需要减去特定的值,例如当我们计算eax * 7时,由于7非二的次幂,我们无法通过lea指令进行计算,但我们可以计算eax * 8计算出的结果减去一个eax同样可以得到正确的值。

  • 假设 eax=3 计算 eax * 7 + 10 的结果,拆分过程如下:
  • 1.计算 lea edx,dword ptr ds:[eax * 8] 这就相当于计算 edx = (8 * eax)
  • 2.计算 sub edx,eax 这就相当于计算 edx = (8 * eax) - eax
  • 3.计算 add edx,10 这就相当于计算 edx = ( (8 * eax) - eax ) + 10
  • 4.经过如上计算,我们就可以计算出eax * 7 + 10的最终结果

这个计算过程看似复杂,但其实在汇编层面并不难构建,如下分别实现计算两个表达式求值过程。

.data
  x DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    ; 针对乘法的lea指令优化
    mov dword ptr ds:[x],3
    
    ; 如果计算乘法时乘数非2的次幂,则此时需要减

    ; 计算 edx = eax * 7 + 10
    mov eax,dword ptr ds:[x]               ; eax = 3 => 计算 eax * 7 + 10
    lea edx,dword ptr ds:[eax * 8]         ; edx = eax * 8
    sub edx,eax                            ; edx = edx - eax
    add edx,10                             ; edx = edx + 10
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 7 + 10
    
    ; 计算 edx = eax * 3 - 7
    mov eax,dword ptr ds:[x]               ; eax = 3 => 计算 eax * 3 - 7
    lea edx,dword ptr ds:[eax * 2]         ; edx = eax * 2
    add edx,eax                            ; edx = edx + eax
    sub edx,7                              ; edx = edx - 7
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 3 - 7
    
    invoke ExitProcess,0
  main ENDP
END main

7.5 使用SHL计算无符号乘法

通过使用逻辑左移同样可以实现2的次幂的高速乘法运算,但逻辑左移只能用于计算无符号乘法,且只能计算被乘数是2的次方的算式。

  • 计算时我们需要参考次方表,这里我列举出几个常用的次方数值:

  • 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128

  • 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384

  • 假设 eax=3 计算 eax * 8 + 10 的结果,拆分过程如下:

  • 1.计算 shl eax,3 这就相当于计算 eax = eax * 2 ^(次方) 3 其公式相当于计算 eax = eax * 8

  • 2.计算 add eax,10 这就相当于计算 eax = (eax * 8) + 10

  • 3.最终即可得到计算结果也就是3*8+10得到34

通过使用逻辑左移,我们可以实现快速无符号乘法运算,如下代码是效率最高的一种。

.data
  x DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],3
    
    ; 计算 eax = eax * 2 ^ 1 相当于计算 eax * 2
    mov eax,dword ptr ds:[x]
    shl eax,1
    invoke crt_printf,addr szFmt,eax
    
    ; 计算 eax = eax * 2 ^ 2 相当于计算 eax * 4
    mov eax,dword ptr ds:[x]
    shl eax,2
    invoke crt_printf,addr szFmt,eax
    
    ; 计算 eax = eax * 2 ^ 3 相当于计算 eax * 8
    mov eax,dword ptr ds:[x]
    shl eax,3
    add eax,10
    invoke crt_printf,addr szFmt,eax

    invoke ExitProcess,0
  main ENDP
END main

7.6 使用SAL计算有符号乘法

通过使用算数左移同样可以实现2的次幂的高速乘法运算,与逻辑左移不同,算术左移只能计算有符号乘法,且只能计算被乘数是2的次方的算式。

  • 计算时我们需要参考次方表,这里我列举出几个常用的次方数值:

  • 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128

  • 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384

  • 假设 eax=-5,ebx=3 计算 (eax * 8) + (ebx * 4) 的结果,拆分过程如下:

  • 1.计算 sal eax,3 这就相当于计算 eax = (eax * 2 ^ 3 ) 其公式相当于计算 eax = eax * 8 结果是一个有符号数

  • 2.计算 shl ebx,2 这就相当于计算 ebx = (ebx * 2 ^2) 其公式相当于计算 ebx = ebx * 4 结果是一个无符号数

  • 3.最终将有符号与无符号数通过 add eax,ebx 相加,即可得到(eax * 8) + (ebx * 4)的最终结果-28

如下是通过算数左移,实现2的次幂的高速乘法运算,我们可以将算数运算与逻辑运算相加通过此方式提高运算效率。

.data
  x DWORD ?
  y DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],-5
    mov dword ptr ds:[y],3
    
    ; 计算 eax = eax * 2 ^ 1 相当于计算 eax * 2
    mov eax,dword ptr ds:[x]
    sal eax,1
    invoke crt_printf,addr szFmt,eax
    
    ; 计算 eax = eax * 2 ^ 2 相当于计算 eax * 4
    mov eax,dword ptr ds:[x]
    sal eax,2
    invoke crt_printf,addr szFmt,eax

    ; 计算 eax = (eax * 2 ^ 3 ) + (ebx * 2 ^2) 相当于计算 (eax * 8) + (ebx * 4)
    mov eax,dword ptr ds:[x]
    mov ebx,dword ptr ds:[y]
    sal eax,3                  ; eax * 8 (有符号乘法)
    shl ebx,2                  ; ebx * 4 (无符号乘法)
    add eax,ebx                ; eax + ebx
    invoke crt_printf,addr szFmt,eax

    invoke ExitProcess,0
  main ENDP
END main

乘法优化的知识点基本就这些,除了两个未知变量的相乘无法优化外,其他形式的乘法运算均可以进行优化,如果表达式中存在一个常量值,那编译器则会匹配各种优化策略,最后对不符合优化策略的运算进行调整,如果真的无法优化,则会使用原始乘法指令计算。文章来源地址https://www.toymoban.com/news/detail-672662.html

到了这里,关于5.7 汇编语言:汇编高效乘法运算的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 汇编语言第一讲:计算机的组织架构和汇编语言介绍

    第一讲:计算机的组织架构和汇编语言介绍 汇编语言 计算机组织架构 数字电路 术语回顾 数制 数字电路 硬件电路 数字电路的问题 汇编语言的开始 程序的节(sections) 调用操作系统的系统调用 列出文件(Listing files) 汇编和链接 调试汇编程序 反汇编现有的程序 附录 课程资源

    2024年04月09日
    浏览(52)
  • 5.10 汇编语言:汇编过程与结构

    过程的实现离不开堆栈的应用,堆栈是一种后进先出 (LIFO) 的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场。 栈是由 CPU 管理的线性内存数组,它使用两个寄存器 (S

    2024年02月11日
    浏览(41)
  • 南京邮电大学汇编语言程序设计实验一(汇编语言语法练习与代码转换)

    排除语法错误:给出的是一个通过比较法完成8位二进制数转换成十进制数送屏幕显示功能的汇编语言源程序,但有很多语法错误。要求实验者按照原样对源程序进行编辑,汇编后,根据TASM给出的信息对源程序进行修改,知道没有语法错误为止。然后进行链接,并执行相应可

    2024年02月08日
    浏览(62)
  • 低级语言汇编真的各个面不如汇编吗?

    今日话题,低级语言汇编真的各个面不如C语言吗?C语言因其可移植性、开发效率和可读性而在各领域广泛使用,市场占有率极高。然而,汇编语言在特定场景下仍然具有独特优势,稳固地占据一席之地。如果你对这方面感兴趣,我可以分享一套包含各类语言和嵌入式行业教

    2024年02月06日
    浏览(48)
  • 在C语言中调用汇编语言的函数

    在C语言中调用汇编文件中的函数,要做的主要工作有两个: 一是在C语言中声明函数原型,并加extern; 二是在汇编中用EXPORT导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr返回。然后,就可以在C语言中使用该函数了。 从C语言的角度,并不知道该函

    2024年02月14日
    浏览(39)
  • 汇编语言学习笔记六

    CF:进位标志位,产生进位CF=1,否则为0 PF:奇偶位,如010101b,则该数的1有3个,则PF=0,如果该数的1的个数为偶数,则PF=1。 0也是偶数 ZF:在相关指令执行后(运算和逻辑指令,传送指令不影响ZF的值),其结果为0,则ZF=1,否则为0。 SF:符号标志位,如果结果为负,则SF=1,否则为

    2024年02月03日
    浏览(44)
  • 汇编语言中断编程步骤

    1、调用movsb指令将中断处理程序载入内存的指定位置; 1)使用offset指令计算doIntEnd-doInt获取中断处理程序的代码长度; 2)doIntEnd位置使用nop指令。 2、修改中断向量表项为指定位置; 1)使用word ptr确定内存单元; 2)使用es=0来定位中断向量表首地址。 3、编写中断处理程序。

    2024年02月07日
    浏览(43)
  • 汇编语言学习笔记四

    字符是以ASCII码的形式存储的,一个字符对应着8为二进制数,2位16进制数。 所以可以得到对应的字符地址。 根据ASCII码,字符的大写和小写相差一个0010 0000,比如a对应的ASCII码是0110 0001,那么A则对应的是0100 0001,对比可以发现他们只是第5位不同,第5位为1,则是小写,否则

    2024年02月03日
    浏览(63)
  • 汇编语言实验——大数相乘

    1.1实验内容 实现两个十进制大整数的相乘(100位以上),输出乘法运算的结果。 1.2实验环境 Microsoft Visual Studio 2017+masm 32 1.3实验思路 1.3.1数据读入 大数相乘由于输入的数字过大而不能用一个dword来存储,所以需要使用数组来存取每一位,每一位大小范围在0-9中,按位读取输入

    2024年02月09日
    浏览(46)
  • ARM汇编语言(2)

    ARM汇编语言是一种低级别的计算机指令集架构(ISA)语言,它是ARM处理器上的一种指令集架构,用于编写底层的系统软件,例如操作系统、驱动程序和嵌入式系统应用程序。 ARM汇编语言使用基于寄存器的指令集,其中指令操作的数据通常存储在处理器的寄存器中,而不是内存

    2024年02月02日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包