记C++坑:5.默认的隐式类型转换

9 篇文章 0 订阅
6 篇文章 0 订阅

最近排查解决一个项目中的性能问题,发现在一个工作线程中有这么一段代码:

uint64_t servive_loops = 0;
while(true)
{
    // 业务逻辑代码
    if (servive_loops % 1000 == 0)  // 标记-1
    {
        RecodeServiceStatistics(m_statistics);
    }
    ++servive_loops;
}

“标记-1”处的代码意思很简单,就是循环1000次记录一下服务的统计信息,本来也是不会有太大的性能影响的但是觉得既然是优化,那么能更进一步的就一步到位好了。于是做了如下修改:

uint64_t servive_loops = 0;
while(true)
{
    // 业务逻辑代码
    if (servive_loops << 54 >> 54 == 0)  // 标记-1
    {
        RecodeServiceStatistics(m_statistics);
    }
    ++servive_loops;
}

这样改的效果是统计的频率差不了太多,从每1000次变成每1024次(还看起来很geek,[滑稽]),并且 << >> 操作比 % 操作要快很多。也算是个不痛不痒的小优化。

本来这样就算结束了,但突然觉得,1024是2^10,10位以内的话为什么要用uint64_t那么长的类型,左移右移那么辛苦,还平白多了那么多次位移,于是再改了一版:

uint16_t servive_loops = 0;
while(true)
{
    // 业务逻辑代码
    if (servive_loops << 6 >> 6 == 0)  // 标记-1
    {
        RecodeServiceStatistics(m_statistics);
    }
    ++servive_loops;
}

这里直接将uint64_t改成uint16_t并不会造成溢出问题,因为unsigned类型达到最大值之后再+1就会变成0,并且最大值也是1024的倍数,恰好也可以保证即便溢出了也会每循环1024次记录一次统计数据。

想着应该没什么问题了,并且还觉得很完美,然而,一跑起来就不对了。。最后记录下的数据总是会比原来的少很多,很纳闷,难道这样改不对?

然后专门写了几句代码测试了一下:

uint16_t a = 1024;
uint16_t b = a << 6 >> 6;
std::cout << b << std::endl;

结果却是1024。。。当时我就震惊了,果然想当然是不行的,然后我又写了下面的代码:

uint64_t c = 1024;
uint64_t d = c << 54 >> 54;
std::cout << d << std::endl;

uint32_t e = 1024;
uint32_t f = e << 22 >> 22;
std::cout << f << std::endl;

结果d和f的值都是0,而为什么b的值就是1024呢。

然后一步步调试,拆解,终于发现了原因:在

a << 6

操作之后得到的类型已经不是uint16_t了,而是隐式转换成了uint32_t。2进制的1024在左移6位之后,原来11位上的1到了17位,如果此时类型是uint16_t,那么其所有位置上都是0,再右移6位依然全0,得到最终结果是0,但是如果是32位就不一样了,左移之后17位上的1得到了保留,再右移6位重新回到了原来所在的11位上,即便最终转成了uint16_t,得到的结果还是1024。

为什么会发生这个隐式转换呢,这个机制嘛,就。。。虽然知道一些理论,但是实际的转换规则还是得自己编译编译调试调试,比如这段代码:

	char x = '1';
	auto y = x  + '1';
	auto t = typeid(y).name();

会发现也默认转成了int。

所以,回到上面的额优化代码上来说,至少代码应该是这样的才对:

uint16_t servive_loops = 0;
while(true)
{
    // 业务逻辑代码
    if (uint16_t(servive_loops << 6) >> 6 == 0)  // 标记-1
    {
        RecodeServiceStatistics(m_statistics);
    }
    ++servive_loops;
}

但是有一个问题是这一个改动又为每次if判断增加了额外的类型转换操作,性能其实是打折扣的,所以,既然c++会默认帮咱将左移后的结果转换成uint32_t,还不如谢绝它的好意,直接就用uint32_t好了,代码修改如下:

uint32_t servive_loops = 0;
while(true)
{
    // 业务逻辑代码
    if (servive_loops << 22 >> 22 == 0)  // 标记-1
    {
        RecodeServiceStatistics(m_statistics);
    }
    ++servive_loops;
}

性能会比其他几种写法都要高出很多。

不过,回头再看,就这么一点小优化,来回折腾好几遍,还真是不值得,不过能够踩到这么一个极其容易被忽略的坑,吃一堑长一智倒也不算太坏。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值