C++之那些年踩过的坑(附录一)

作者:刘俊延(Alinshans)

本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑。以此作为给自己的警惕。


【版权声明】转载请注明原文来自:http://www.cnblogs.com/GodA/p/6639526.html

本来上个月就开始动笔了,直到现在才发出来,实在太多事情。可能有些小朋友不知道写这一篇随笔的起因,那么你可以看一下我之前写的。

上一篇的最后,我提到了一个问题:代码优化。并留了一个小测试:无符号数与有符号数的性能比较。不知道有没有人去实验。我做了一个简单的测试:

#include <iostream>
#include <chrono> int main()
{
/*unsigned */int a = ;
auto t1 = std::chrono::system_clock::now();
for (int i = ; i < ; ++i)
{
a += ;
a -= ;
a *= ;
a /= ;
}
auto t2 = std::chrono::system_clock::now();
auto runtime = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1);
std::cout << "a = " << a <<"\n";
std::cout << runtime.count() << "ns";
}

在上述代码在 VS 的 Debug 模式下运行(Release就优化掉了),稳定后运行时间在 2800ns,然后把注释去掉,再次运行,稳定后运行时间还是 2800ns。在我在电脑上,计算有符号类型和无符号类型几乎是没有差别的,我相信在绝大多数的电脑上也是相同的结果。

类似的还有浮点数的计算比整型数慢?对于这个问题,可以看一下这个问题:https://www.zhihu.com/question/26494062

首先我要声明:我不是反对代码优化。对于有很多流传广泛的所谓的优化技巧,我觉得我们应该应该抱着学习探索的心态,而不是一味地追求一些没有什么意义的东西。有些优化,确实很精妙。但很多所谓的技巧,看起来的意思就是:做编译器的那群人都是傻逼。想优化我们的程序,这是正常的、应该的想法,但我们应该用科学的方法,而不是听了一些奇淫技巧,却不知道里面发生了什么。

其实很简单,探究性能瓶颈靠 profiling,探究代码背后的不为人知的故事看 assembly。我们先讲后面一个。

我想大多数人还是在 Windows 下编程,所以用的肯定也是宇宙最强 IDE VS。在 VS 下看汇编很简单,随便设置一个断点,然后按调试下面的开始调试,然后在打开在调试下的窗口找到反汇编,就可以看了。比如我们研究有符号数和无符号数,先写一个程序:

int main()
{
/*unsigned */int a = ;
a = a / ;
std::cout << a;
}

然后按照刚刚的方法(在 Debug 下)看反汇编:

  /*unsigned */int a = ;
00B4104E mov dword ptr [a],75BCD15h
a = a / ;
00B41055 mov eax,dword ptr [a]
00B41058 cdq
00B41059 mov ecx,0Dh
00B4105E idiv eax,ecx
00B41060 mov dword ptr [a],eax

然后在把注释去掉试试:

  unsigned int a = ;
00C3104E mov dword ptr [a],75BCD15h
a = a / ;
00C31055 mov eax,dword ptr [a]
00C31058 xor edx,edx
00C3105A mov ecx,0Dh
00C3105F div eax,ecx
00C31061 mov dword ptr [a],eax

几乎是一模一样的,最大的差别就是有符号数使用 idiv指令(带符号除法),无符号数使用 div指令(不带符号除法),而这两种指令,CPU周期都是一样的。http://www.agner.org/optimize/instruction_tables.pdf

当然我不是说不用无符号数,而是说我们用什么要看场合,而不是你觉得用了性能更好,除非是被大众认可的或者你经过严谨的测试的。像对于某些书籍或者什么地方说,只要确定范围不为负数的,就用无符号类型,我是不认可的。如果你讲范围,那如果一个有符号类型不够用,那么通常(相同bit下)它对应的无符号类型也不够用。比如你 int32_t 不够用,就应该用 int64_t,如果还不够,考虑写个 BigInteger类吧 :) 。不过对于无符号和有符号类型,它们之间的性能在当代确实是几乎没有什么差别。那具体什么场合用什么呢?这个也不一定,比如一般来说:

  • 对于位储存、位运算、模运算等,使用无符号类型
  • 对于一般运算使用有符号类型

其实我对于无符号有符号是觉得很那个什么的。。平时我们说一个数,要么就是整数,要么就是小数得了,还要去分有没有符号那真的是。不过因为是 C++ 所以也就没什么奇怪的了。

好像有点偏离我想说的主题了(拽回来

我并不是想专门讲这个什么有符号无符号,而是想借这个题引出(我的)一些看法:

  • 过早的优化是万恶之源
  • 不要试图帮编译器优化
  • 优化时不要去猜测,想当然得去优化自己“觉得”性能差的地方
  • 探究性能的瓶颈靠 profiling

我们一点一点讲。

对于一个需求,我们应该先完成功能,若性能达不到要求之后,在确定瓶颈之后再去优化。过早优化,不仅让代码不直接,还容易出bug,还可能对性能几乎没有影响。而且,我们优化时,应该关注大方向,确定大方向是正确的。比如写一个算法,我们首先应该确保 Big-O 的时间复杂度能达标,可以用 O(n) 的就不用 O(nlogn),可以用 O(nlogn) 的就不用 O(n²),而不是先在那里扣 i++ 还是 ++i。另外,不要想着去帮编译器优化,因为编译器是一堆比你强不知道多少的人写出来的,而对于一般人,想着去帮编译器优化,大部分是无效的,少部分是错的。比如有人学了一点点 std::move,就老是想着 move move move 去提高性能,举个栗子,(不同的)容易写出这样的代码:

template <typename T>
T create()
{
auto object = new T();
return std::move(object);
}

确实运行不会错,但是,代码背后做的事情不一定就跟你想的一样,往往跟你想象的还不一样。有些情况编译器可以采用更好的办法,结果因为你那么一搞,迫不得已只能采用次一点的办法。可以看看 这个问题,不赘述了。
还有比如说用异或来交换两个变量,有人就会想,用位运算,不仅不需要创建临时变量,而且位运算一般不是更快嘛!对于这个问题,陈硕大大早有讲到,在 https://cloud.github.com/downloads/chenshuo/documents/CppPractice.pdf 的第 9 章。要是没有看过的劳烦读者自行前往。

如果你已经看了上面的链接,那么你也就知道了,你(几乎)不会知道编译器做了什么,编译器可以做的优化超出你的想象(不过有的时候人能明显看出来的优化编译器却做不到,但影响不大),在我的系列文章(二)中也反复强调了,不要试图帮助编译器去优化。若你想探究一小段代码背后的细节,就去看反汇编吧!

然后关于探究性能瓶颈,我还是想贴上一个回答:https://www.zhihu.com/question/56727144/answer/150555866

最后我希望我放的链接都有认真看呀!为什么我贴那么多链接呢?因为我说的话没有权威没人信啊!大神说的总有一点参考价值了吧!(

好了,暂时写那么多先。

才学疏浅,如有不当地方还请海涵,感谢指点!

《C++之那些年踩过的坑(附录一)》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. ycsb对hbase性能测试的研究

    ycsb对hbase性能测试 1.ycsb安装 下载ycsb软件 wget https://github.com/downloads/brianfrankcooper/YCSB/ycsb-0.1.4. ...

  2. Springboot启动源码详解

    我们开发任何一个Spring Boot项目,都会用到如下的启动类 @SpringBootApplication public class Application { public static voi ...

  3. [HDU1210] Eddy's 洗牌问题

    Problem Description Eddy是个ACMer,他不仅喜欢做ACM题,而且对于纸牌也有一定的研究,他在无聊时研究发现,如果他有2N张牌,编号为1,2,3..n,n+1,..2n.这也是 ...

  4. StringBuffer与StringBuilder的区别,及实现原理

    区别 1.StringBuffer 与 StringBuilder 中的方法和功能完全是等价的, 2.只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是 ...

  5. html5常用英语单词

    Aabsolute 绝对active 激活的align 对齐alpha 半透明度animation 卡通片绘制auto 自动aside 偏栏 Bbackground 背景bgcolor 背景颜色blo ...

  6. 记录Winform开发过程中遇到的情况

    前两天开发了个Winform操作Excel和数据库的一个小程序,把Winform的一些东西又给捡了起来,当中又学到了一些新的东西,特来写出来留作纪念. 一.CSKIN美化框架的使用 刚开始做的时候,发 ...

  7. idea 中设置成公司规范的代码格式

    优雅的编码格式是一个程序员的必备素质. 最近切换到了 idea,想对自己的代码进行格式化的时候希望能自动排版成公司规定的格式,可以做以下设置: 打开 idea 的 preference: 左侧找到 c ...

  8. 第一次"正经面试"之发现自己的缺陷和不足

    1:初试之校园招聘~~~ 如果你细心,可能发现了"正经面试"加了双引号,说起这次面试(昨天面的技术试),要从上礼拜六,距今已经一礼拜了吧.现在这个时候校园招聘已经蠢蠢欲动了吧,(说 ...

  9. 搭建高可用mongo集群3.4版本

    搭建高可用mongo集群3.4版本 说在开始之前:在搭建这个环境之前,已经有了一个师兄搭好的环境,虽然一样很棒,但是没有经过自己的手出来的东西,还是不属于自己,所以摸索着自己搭建一个吧,好巧不巧的是, ...

  10. C#传递委托给C或C++库报错__对XXX类型的已垃圾回收委托进行了回调

    出现的原因: 因为你传给C或C++的委托是局部的.可能传过去之后就被垃圾回收了,再次调用就会异常. 想办法做成全局的就好 public void Play(string url) { _bassStr ...