百科问答小站 logo
百科问答小站 font logo



C语言 乘以0.01快?还是除以100快? 第1页

  

user avatar   bei-ji-85 网友的相关建议: 
      

整理并且统一回复一下评论里的一些观点:

其实题主问的一个核心问题是,等效的乘法和除法,哪个更快。这里问题有一个前提:如果是数学上的等效的乘除法,必然有一个是浮点计算,所以我的回答的结论其实是正确的:浮点操作几乎在任何时候都比整数操作要慢

注意这里的几乎,例外情况包括:流水线情况、cache的情况、编译器优化、代码如何编写方式等等。

评论里有人质疑latency是否能完全代表性能,latency肯定不能代表实际性能,但已经没有更好的尺度来衡量指令的性能了,所以latency几乎是除了真机实验以外的最好的尺子。

同时我的回答里,另一个观点也是很重要的:浮点乘除本身的性能跟整数差别不大,浮点操作会涉及到整数与浮点之间的转换,这里的指令开销要远远大于乘除计算本身

找一份Intel或者AMD的相关手册可以查到指令集的性能,一个大概的结论就是,从乘除指令的层面上看,按指令开销从小到大是:整乘 < 浮点乘 < 浮点除 < 整除

单个指令,整除是最慢的,但多数情况下,立即数的除法会被优化掉(注:IDIV的latency在20-27之间,多数编译器都会优化成其他算法),只有两个变量做除法的时候,才有可能出现最糟糕的性能。所以如果从整体考虑,等效乘除,就意味着必然有整数与浮点之间的互转,这里通常需要额外5-6条浮点指令,开销远远大于整数除法。

所以就有了最终结论,浮点要慢于整数,开销大头不在乘除本身。

---------------原回答---------------

先说结论,默认编译条件下,整数除法比浮点乘法要快。然而,开销的大头并不在乘除本身。

反汇编代码如下:

           int k = y * 0.01;  614:   8b 45 dc                mov    -0x24(%ebp),%eax  617:   89 45 d0                mov    %eax,-0x30(%ebp)  61a:   db 45 d0                fildl  -0x30(%ebp)  61d:   dd 83 70 e7 ff ff       fldl   -0x1890(%ebx)  623:   de c9                   fmulp  %st,%st(1)  625:   d9 7d d6                fnstcw -0x2a(%ebp)  628:   0f b7 45 d6             movzwl -0x2a(%ebp),%eax  62c:   80 cc 0c                or     $0xc,%ah  62f:   66 89 45 d4             mov    %ax,-0x2c(%ebp)  633:   d9 6d d4                fldcw  -0x2c(%ebp)  636:   db 5d e0                fistpl -0x20(%ebp)  639:   d9 6d d6                fldcw  -0x2a(%ebp)     int m = y / 100;  63c:   8b 4d dc                mov    -0x24(%ebp),%ecx  63f:   ba 1f 85 eb 51          mov    $0x51eb851f,%edx  644:   89 c8                   mov    %ecx,%eax  646:   f7 ea                   imul   %edx  648:   c1 fa 05                sar    $0x5,%edx  64b:   89 c8                   mov    %ecx,%eax  64d:   c1 f8 1f                sar    $0x1f,%eax  650:   29 c2                   sub    %eax,%edx  652:   89 d0                   mov    %edx,%eax  654:   89 45 e4                mov    %eax,-0x1c(%ebp     

浮点乘法用的是fmulp,整数除法用的是imul,在Intel Haswell平台上fmulp的Latency是5,imul的Latency是3,虽然有差距,但也不是差距特别巨大的,反倒是其它浮点指令,比如fild的Latency是6,而整数的mov指令,实际可能都不需要一个cycle就完成。所以最终的整数计算的速度可能是浮点数的几倍。

把Latency标注出来如下(注:不一定代表真实效率,但关联度很大):

浮点:

        614:   8b 45 dc                mov    -0x24(%ebp),%eax     ### Latency 3  617:   89 45 d0                mov    %eax,-0x30(%ebp)     ### Latency 2 * 0.5  61a:   db 45 d0                fildl  -0x30(%ebp)          ### Latency 6  61d:   dd 83 70 e7 ff ff       fldl   -0x1890(%ebx)        ### Latency 3 * 0.5  623:   de c9                   fmulp  %st,%st(1)           ### Latency 5  625:   d9 7d d6                fnstcw -0x2a(%ebp)          ### Latency 6  628:   0f b7 45 d6             movzwl -0x2a(%ebp),%eax     ### Latency 3 * 0.5  62c:   80 cc 0c                or     $0xc,%ah             ### Latency 1 * 0.25  62f:   66 89 45 d4             mov    %ax,-0x2c(%ebp)      ### Latency 2 * 0.5  633:   d9 6d d4                fldcw  -0x2c(%ebp)          ### Latency 7  636:   db 5d e0                fistpl -0x20(%ebp)          ### Latency 6  639:   d9 6d d6                fldcw  -0x2a(%ebp)          ### Latency 7     

整数:

        63c:   8b 4d dc                mov    -0x24(%ebp),%ecx     ### Latency 3  63f:   ba 1f 85 eb 51          mov    $0x51eb851f,%edx     ### Latency 1 * 0.25  644:   89 c8                   mov    %ecx,%eax            ### Latency 1 * 0.25  646:   f7 ea                   imul   %edx                 ### Latency 3  648:   c1 fa 05                sar    $0x5,%edx            ### Latency 1 * 0.5  64b:   89 c8                   mov    %ecx,%eax            ### Latency 1 * 0.25  64d:   c1 f8 1f                sar    $0x1f,%eax           ### Latency 1 * 0.5  650:   29 c2                   sub    %eax,%edx            ### Latency 1 * 0.5  652:   89 d0                   mov    %edx,%eax            ### Latency 1 * 0.25  654:   89 45 e4                mov    %eax,-0x1c(%ebp)     ### Latency 2 * 0.5     

对比一下,就可以看出来浮点数的性能比整数要差得多。


统一回复一下评论的疑问:

y是int,从键盘输入

       #include <stdio.h> int main() {     int y;     scanf("%d", &y);     int k = y * 0.01;     int m = y / 100;     printf("%d,%d
", k, m);     return 0; }     

latency数据来源于第三方手册


user avatar   li-tong-zhou-18-83 网友的相关建议: 
      

乘以0.01和除以100哪个快?

在知乎上看到这个问题,觉得挺有趣的。下面的回答五花八门,但是就是没有直接给出真正的benchmark结果的。还有直接搬反汇编代码的,只不过汇编里用了 x87 FPU 指令集,天那这都 202x 还有程序用这个老掉牙的浮点运算指令集的吗?

我也决定研究一下,读反汇编,写benchmark。平台以 x86-64 为准,编译器 clang 13,开编译器优化(不开优化谈速度无意义)

代码及反汇编

gcc.godbolt.org/z/rvT9n

首先讨论整数的情况,首先是整数除100

       int int_div(int num) {     return num / 100; }     

结果为

       int_div(int):                            # @int_div(int)         movsxd  rax, edi         imul    rax, rax, 1374389535         mov     rcx, rax         shr     rcx, 63         sar     rax, 37         add     eax, ecx         ret     

稍作解释。edi为函数的第一个整数参数(x64调用约定);imul为有符号整数乘法;shr为逻辑右移(符号位补0);sar为算数右移(符号位不变)

可以看到编译器将除法编译为乘法和位运算,意味着编译器认为这么一大串指令也比除法指令快。代码里一会算术右移一会逻辑右移是为了兼容负数。如果指定为无符号数,结果会简单一些

       unsigned int_div_unsigned(unsigned num) {     return num / 100; }     

结果为

       int_div_unsigned(unsigned int):                  # @int_div_unsigned(unsigned int)         mov     eax, edi         imul    rax, rax, 1374389535         shr     rax, 37         ret     

也可以强制让编译器生成除法指令,使用volatile大法

       int int_div_force(int num) {     volatile int den = 100;     return num / den; }     

结果为

       int_div_force(int):                     # @int_div_force(int)         mov     eax, edi         mov     dword ptr [rsp - 4], 100         cdq         idiv    dword ptr [rsp - 4]         ret     

稍作解释,edi是第一个整数参数(x64调用约定),cdq是有符号32位至64位整数转化,idiv是有符号整数除法。 整数除法指令使用比较复杂。首先操作数不能是立即数。然后如果除数是32位,被除数必须被转化为64位,cdq指令是在做这个转化(因为有符号位填充的问题)。另外汇编里出现了内存操作,这是volatile的负作用,会对结果有些影响。

下面是整数乘0.01

       int int_mul(int num) {     return num * 0.01; }     

结果为

       .LCPI3_0:         .quad   0x3f847ae147ae147b              # double 0.01 int_mul(int):                            # @int_mul(int)         cvtsi2sd        xmm0, edi         mulsd   xmm0, qword ptr [rip + .LCPI3_0]         cvttsd2si       eax, xmm0         ret     

稍作解释,edi是第一个整数参数(x64调用约定),cvtsi2sd是整数到双精度浮点数转换(ConVerT Single Integer TO Single Double),mulsd是双精度整数乘法,cvttsd2si是双精度浮点数到整数转换(截断小数部分),xmm0为SSE2的32位寄存器 因为没有整数和浮点数运算的指令,实际运算中会先将整数转换为浮点数,运算完毕后还要转回来。计算机中整数和浮点数存储方法不同,整数就是简单的补码,浮点数是IEEE754的科学计数法表示,这个转换并不是简单的位数补充。

下面讨论浮点数的情况。首先是浮点数除100

       double double_div(double num) {     return num / 100; }     

结果为

       .LCPI4_0:         .quad   0x4059000000000000              # double 100 double_div(double):                        # @double_div(double)         divsd   xmm0, qword ptr [rip + .LCPI4_0]         ret     

稍作解释,xmm0是第一个浮点数参数(x64调用约定),divsd是SSE2双精度浮点数除法。因为SSE寄存器不能直接mov赋值立即数,立即数的操作数都是先放在内存中的,即qword ptr [rip + .LCPI4_0]

然后是浮点数乘0.01

       double double_mul(double num) {     return num * 0.01; }     

结果为

       .LCPI5_0:         .quad   0x3f847ae147ae147b              # double 0.01 double_mul(double):                        # @double_mul(double)         mulsd   xmm0, qword ptr [rip + .LCPI5_0]         ret     

结果与除法非常接近,都只有一个指令,不需要解释了

Benchmark

quick-bench.com/q/1rmqh

结果为(按照用时从小到大排序)

  1. 浮点数乘
  2. 无符号整数除(编译为乘法和移位)
  3. 有符号整数除(编译为乘法和移位)
  4. 整数乘(用时与编译为乘法的整数除十分接近)
  5. 强制整数除
  6. 浮点数除

其中用时最多的浮点数除和浮点数乘用时相差14倍。另外值得一提的是,如果启用 --ffast-math 编译参数,编译器会把浮点数除编译为浮点数乘


user avatar   pansz 网友的相关建议: 
      

回答下问题的后半部分——如何避免字体侵权?

大家可以试试阿里巴巴普惠体。

就是戳这里

2年前,我们发布了“阿里巴巴普惠体”,免费向全社会开放下载和使用。

让大家开淘宝店也好、做小生意也好,不用再为字体版权而苦恼了。

现在我们在原有基础上,优化了部分字体结构、新增了4个字重(字体粗细)。

还新增了泰文和越南文。

希望大家做生意更方便,做设计更顺手

当然最重要的是,阿里巴巴普惠体开放给全社会免费下载和使用,所有平台可永久免费商用

过去免费,现在免费,永久免费

( ̄y▽ ̄)~❤️




  

相关话题

  苹果的 Rosetta 2 的实现原理大概是怎样的? 
  有哪些结合医学、计算机、人工智能的研究领域? 
  为什么说指针是 C 语言的精髓? 
  按下开机键后,电脑都干了些什么? 
  运算符+左右操作数地位相等吗? 
  为什么只有计算机专业非常看重顶会,而其他专业对顶会并不认可? 
  在人工智能时代,孩子学编程有什么好处? 
  在理想状态下,CPU需不需要工作在一个时钟下? 
  为什么C++在构造函数和析构函数中不能抛出异常? 
  如何用C语言生成(0,1)之间的随机浮点数? 

前一个讨论
济南铁路局的D6020次列车为什么开行时间这么早?除动检车外,这是全国最早的一班高铁或动车吗?
下一个讨论
特斯拉选择 Linux 作为车载系统的原因是什么?





© 2024-05-16 - tinynew.org. All Rights Reserved.
© 2024-05-16 - tinynew.org. 保留所有权利