搜索
bottom↓
回复: 17

请教:除法运算表达方式不一样会这么影响运行速度吗?

[复制链接]

出0入0汤圆

发表于 2021-2-26 15:49:03 | 显示全部楼层 |阅读模式
本帖最后由 prince2010 于 2021-2-26 16:01 编辑

直接上代码——

void TIM2_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);

    TIM_DeInit(TIM2);

    TIM_TimeBaseStructure.TIM_Period = 65536 - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

//    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
}

u32 TimeIn_us, TimeOut_us, Time_us, Time_us_Max;


#define LoopTimeTestIn          TimeIn_us = TIM2 -> CNT;


#define LoopTimeTestOut         TimeOut_us = TIM2 -> CNT;        \
                                if(TimeOut_us < TimeIn_us) TimeOut_us += 65536; \
                                Time_us = TimeOut_us - TimeIn_us;   \
                                if(Time_us_Max < Time_us)Time_us_Max = Time_us;

u32 funA(u32 a, u64 b)
{
    return (a- (b>> 8)) / 350;

}

u32 funB(u32 a, u32 b)
{
    return (a- b) / 350;

}


void testspeed(void)
{
    while(1)
    {
        LoopTimeTestIn

        funA(0x8f5050a6, 0x8f4f70b2);
//        funB(0x8f5050a6, 0x8f4f70b2>>8);

        LoopTimeTestOut
        
        printf("Time_us:%uus, Time_us_Max:%uus\r\n", Time_us, Time_us_Max);
        
        delay_ms(100);
    }
}


现象:运行funA,Time_us_Max大约44us,而运行funB,Time_us_Max大约1us都不到,相差如此之大!

很好奇:写除法表达式需要这么讲究吗?


说明:都没有开优化等级。




阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入42汤圆

发表于 2021-2-26 16:14:50 | 显示全部楼层
funA多做了一次位移,而funB你测试入口的是常数在编译时已做完位移,程序中实际没有做>>8位移

出0入0汤圆

发表于 2021-2-26 16:17:00 | 显示全部楼层
funcA是u64位操作当然慢了,编译器是调用大段程序软件实现的
funcB是32位操作,硬件支持的话就几个周期就搞定了

出0入31汤圆

发表于 2021-2-26 16:18:18 来自手机 | 显示全部楼层
看汇编结果吧,更直观

出0入93汤圆

发表于 2021-2-26 16:27:27 | 显示全部楼层
改为
  1. u32 funA(u32 a, u64 b) {
  2.     return (a - (u32)(b >> 8)) / 350;
  3. }
复制代码
你就会发现相差不大了。反正b>>8超出u32后得到的结果是错的(会溢出),那就干脆别理好了

或者开最高等级优化,除数是常数的会被优化成乘法和移位,而乘法指令只要一个周期,硬件除法还要好多个周期呢。这样的话64位除法也很快的。

出0入0汤圆

 楼主| 发表于 2021-2-26 17:01:29 | 显示全部楼层
takashiki 发表于 2021-2-26 16:27
改为你就会发现相差不大了。反正b>>8超出u32后得到的结果是错的(会溢出),那就干脆别理好了

或者开最高 ...

加了强制类型转换确实有效,没想到不加影响会这么大

出0入0汤圆

 楼主| 发表于 2021-2-26 17:06:43 | 显示全部楼层
nanfang2000 发表于 2021-2-26 16:17
funcA是u64位操作当然慢了,编译器是调用大段程序软件实现的
funcB是32位操作,硬件支持的话就几个周期就搞 ...

确实是这个原因

出0入0汤圆

发表于 2021-2-27 11:18:20 | 显示全部楼层
看看汇编代码,可能原因是:fuc A是64位除法,用的软件除法,而func B是32位除法,默认会使用硬件除法器。

出300入477汤圆

发表于 2021-2-27 19:04:32 来自手机 | 显示全部楼层
desertsailor 发表于 2021-2-27 11:18
看看汇编代码,可能原因是:fuc A是64位除法,用的软件除法,而func B是32位除法,默认会使用硬件除法器。 ...

这是很明显的事情。64位除法在32位机器上只能是软件除法,而32位除法可以是硬件直接执行,甚至除常数可以优化成乘法加移位,更快。
64位除32位常数的除法,虽然原则上也能优化成乘法,但因为很少用,估计编译器不一定做这种优化。就算优化成乘法,64位乘法加移位也比32位的要慢的多

出0入93汤圆

发表于 2021-2-27 20:04:38 来自手机 | 显示全部楼层
redroof 发表于 2021-2-27 19:04
这是很明显的事情。64位除法在32位机器上只能是软件除法,而32位除法可以是硬件直接执行,甚至除常数可以 ...

对于除数是常数的无符号除法,Cortex M的乘法指令UMULL支持64位×32位,单周期,比32位/32位硬件除法要快,移位也仅仅是MOV指令就足够了。所以优化后的结果还真不好说,基于代码大小优化的话硬件除法说不定还会输

出300入477汤圆

发表于 2021-2-27 21:01:30 来自手机 | 显示全部楼层
takashiki 发表于 2021-2-27 20:04
对于除数是常数的无符号除法,Cortex M的乘法指令UMULL支持64位×32位,单周期,比32位/32位硬件除法要快 ...

我不信。
32位arm没见过带64位x32位指令的。64位x32位结果是96位啊!
正常的32位乘法是2个32位的乘起来得到64位。可以用作32位除32位常数的优化。
64位除32位的除法实际上就是64位除64位,要优化成乘法也得用64位乘64位,也就是4个32位乘法再加几个加法。

出300入477汤圆

发表于 2021-2-27 21:05:02 来自手机 | 显示全部楼层
takashiki 发表于 2021-2-27 20:04
对于除数是常数的无符号除法,Cortex M的乘法指令UMULL支持64位×32位,单周期,比32位/32位硬件除法要快 ...

刚查过,UMULL就是常规的乘法,无符号32位乘32位等于64位。

出0入93汤圆

发表于 2021-2-28 06:23:17 | 显示全部楼层
redroof 发表于 2021-2-27 21:05
刚查过,UMULL就是常规的乘法,无符号32位乘32位等于64位。


你说得对,是我记错了。我记得Cortex M中int*int结果只能是int,高32位被舍弃;要想变成long long必须强制转换一个因数,编译后汇编代码会生成一条UMULL/SMULL而不是平时的MUL指令。继续测试发现64位*64位会生成一条UMULL和2条MLA指令,结果为64位,高64位被舍弃了。使用UMLAL/SMLAL指令可以继续搞到96位以及更大,但是编译器生成不了,得自己手动内联汇编。
64位除以常数也不会优化成乘法+移位,还是慢到要死的_ll_udiv,在不溢出的情况下手动优化成乘法理论上才有可能和硬件除法指令(2~16个指令周期)比肩甚至超过的可能性。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出300入477汤圆

发表于 2021-2-28 08:58:06 来自手机 | 显示全部楼层
takashiki 发表于 2021-2-28 06:23
你说得对,是我记错了。我记得Cortex M中int*int结果只能是int,高32位被舍弃;要想变成long long必须强 ...

按C的规定,32位x32位=32位,64位x64位=64位
但是CPU的硬乘法指令,32位x32位实际得到64位结果。

出0入93汤圆

发表于 2021-2-28 09:06:07 | 显示全部楼层
redroof 发表于 2021-2-28 08:58
按C的规定,32位x32位=32位,64位x64位=64位
但是CPU的硬乘法指令,32位x32位实际得到64位结果。 ...

ARM的乘法指令感觉比较奇葩,32位x32位实际得到的结果与指令有关,M3一共有7条指令:MUL、MLA、MLS、UMULL、SMULL、UMLAL、SMLAL。前面3条是32位结果,后面4条是64位的。而且32位结果的乘法(单乘法不涉及乘加)不管是有符号还是无符号都是MUL,其他6条则区分无符号和有符号。

出300入477汤圆

发表于 2021-2-28 09:14:02 来自手机 | 显示全部楼层
本帖最后由 redroof 于 2021-2-28 09:19 编辑
takashiki 发表于 2021-2-28 09:06
ARM的乘法指令感觉比较奇葩,32位x32位实际得到的结果与指令有关,M3一共有7条指令:MUL、MLA、MLS、UMUL ...


没毛病,就是为了实现C的语义。
乘法取低位并累加就是为了方便实现64位乘64位=64位
这种乘法的算法:
低位乘低位得64位结果,高位乘低位取低,加到结果高位,低位乘高位取低加到结果高位。
这样就是正确的算法。
32位乘法根本不区分有无符号,都有或都无,结果是一样的。
只有用32位算64位的中间过程要区分,因为低一半永远是有符号的,高一半如果无符号,就得单独的指令

出0入0汤圆

 楼主| 发表于 2021-2-28 20:02:21 | 显示全部楼层
redroof 发表于 2021-2-28 09:14
没毛病,就是为了实现C的语义。
乘法取低位并累加就是为了方便实现64位乘64位=64位
这种乘法的算法:

兄弟研究得很深啊

出300入477汤圆

发表于 2021-2-28 21:54:56 来自手机 | 显示全部楼层
prince2010 发表于 2021-2-28 20:02
兄弟研究得很深啊

你列个竖式就知道了,算两位数的乘法就是这么算的。
32位机器算64位乘法就是你手算两位数乘法的做法,每个32位等于你列竖式的1位。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 11:04

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表