整理并且统一回复一下评论里的一些观点:
其实题主问的一个核心问题是,等效的乘法和除法,哪个更快。这里问题有一个前提:如果是数学上的等效的乘除法,必然有一个是浮点计算,所以我的回答的结论其实是正确的:浮点操作几乎在任何时候都比整数操作要慢。
注意这里的几乎,例外情况包括:流水线情况、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数据来源于第三方手册
在知乎上看到这个问题,觉得挺有趣的。下面的回答五花八门,但是就是没有直接给出真正的benchmark结果的。还有直接搬反汇编代码的,只不过汇编里用了 x87 FPU 指令集,天那这都 202x 还有程序用这个老掉牙的浮点运算指令集的吗?
我也决定研究一下,读反汇编,写benchmark。平台以 x86-64 为准,编译器 clang 13,开编译器优化(不开优化谈速度无意义)
https:// gcc.godbolt.org/z/rvT9n EE9Y
首先讨论整数的情况,首先是整数除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
结果与除法非常接近,都只有一个指令,不需要解释了
https:// quick-bench.com/q/1rmqh uLLUyxRJNqSlcJfhubNGdU
结果为(按照用时从小到大排序)
其中用时最多的浮点数除和浮点数乘用时相差14倍。另外值得一提的是,如果启用 --ffast-math 编译参数,编译器会把浮点数除编译为浮点数乘
回答下问题的后半部分——如何避免字体侵权?
大家可以试试阿里巴巴普惠体。
就是戳这里
2年前,我们发布了“阿里巴巴普惠体”,免费向全社会开放下载和使用。
让大家开淘宝店也好、做小生意也好,不用再为字体版权而苦恼了。
现在我们在原有基础上,优化了部分字体结构、新增了4个字重(字体粗细)。
还新增了泰文和越南文。
希望大家做生意更方便,做设计更顺手
当然最重要的是,阿里巴巴普惠体开放给全社会免费下载和使用,所有平台可永久免费商用
过去免费,现在免费,永久免费
( ̄y▽ ̄)~❤️