C++程序常见的性能调优方式

最近做了一段时间的程序性能调优,写个帖子总结一下。

当然,都还是一些比较初级层面优化,主要是针对一些我称之为“弱性能代码”的优化,还达不到架构优化的高度和算法优化的深度。

个人涉及过的一些优化手段主要有下面几个方面(有些优化手段不仅仅适用于C++程序,比如多重过滤、提前计算等):

冗余的变量拷贝

相对C而言,写C++代码经常一不小心就会引入一些临时变量,比如函数实参、函数返回值。在临时变量之外,也会有其他一些情况会带来一些冗余的变量拷贝。

多重过滤

很多服务都会过滤的部分结果的需求,比如游戏交谈中过滤需要过滤掉敏感词。假设现在有两个过滤词典,一个词典A内容较少,另一个词典B内容较多,现在有1000个词需要验证合法性。

词落在词典A中的概率是1%,落在词典B中的概率是10%,而判断词是否落在词典A或B中的操作耗时差不多,记作N。

那么要判断词是否合法,有两种方式:

  1. 先判断词是否在A中,如果在返回非法;如果不在再判断是否在B中,如果在返回非法,否则返回合法。
  2. 和方式一类似,不过是先判断是否在B中。

现在我们来计算两种方式的耗时:

  1. 1000*N+1000*(1-1%)*N
  2. 1000*N+1000 …
more ...

性能测试随想

分类

性能测试: 系统常规状态运行时,关注机器资源耗用、响应时间等指标。(可能需要长时间保持系统这个状态,观察是否会有资源泄漏)

容量测试: 关注系统单位时间内能够处理的最大请求数

过载测试: 关注系统过载时系统能够提供的服务。理想的情况是系统仍然可以提供自身容量的服务。

峰谷测试: 关注系统从高负载恢复、转为几乎空闲、然后再攀升到高负载、再降低的能力。比如可以观察第二个高负载和第一个高负载时期系统性能指标是否有差异、在负载强烈波动的情况下是或否会有资源泄漏等。

类似的场景还有很多,个人觉得具体还要看被测系统在实际的业务中是否会出现这些场景,不一定要覆盖所有的性能测试子分类。上面列出来的都是我接触过的一些测试场景。

性能测试常见的关注指标:

业务指标:吞吐量(Throughput)和响应时间(或者叫延迟Latency)

系统资源:CPU空闲率、内存使用、网络IO、磁盘读写量、句柄数等等

并发数、QPS、吞吐量的关系:

并发数:有多少用户同时访问我们的服务,注意这并不代表这么多用户一直在请求我们的服务,因为:

  1. 一般用户会等待服务的响应之后才会发起下一次请求
  2. 用户多个操作之间存在“思考时间”

QPS:是指服务器实际每秒处理的请求数,和吞吐量稍不同的是,它是一个实际值,可能会随并发数变化而变化 …

more ...

查看运行中的python脚本的堆栈

对于c/c++程序,我们可以在运行过程中通过pstack来查看程序当前的执行堆栈。

那么对于python脚本呢?

方法一

如果脚本是前台运行,可以直接Ctrl+c中止该脚本,即可查看当前的执行堆栈。

如果脚本是后台运行的,可以先fg jobid,然后直接Ctrl+c中止脚本。当前的执行堆栈会被打印到脚本后台运行时的输出中 如果是./test.py &运行则是输出到前台;如果是nohup ./test.py &运行则是输出到nohup.out;如果加了输出重定向,则是打印到重定向的输出文件中

方法二:

通过pdb完成,主要是为python脚本设置一个signal_handler,在其hang住的时候发送信号给它,然后进入debug模式。这时可以用w命令打印出当前执行堆栈。

参考这篇文章

不过这个方式,对后台运行的程序无效(不管是否先将程序转到前台)。而且需要提前加部分代码,对于已经在运行中的脚本无效。

好处是,如果程序是前台运行的,进入debug模式之后还可以继续运行,而不像方法一直接就终止脚本了。(有一点要注意的是:如果脚本处于sleep状态,经过信号处理之后sleep就被唤醒了)

方法三 …

more ...

crontab使用

crontab格式:

minute hour day-of-month month-of-year day-of-week [username] command

如果某用户使用crontab -e增加了定时任务,那么系统会使用该用户身份执行该任务。所以username不需要填,填了会被认为是command。

附几个常用用法,有助于理解定时任务的配置:

01 * * * * run_hourly
*/5 * * * * run_every_5mins
02 4 * * * run_daily
22 4 * * 0 run_weekly
42 4 1 * * run_monthly

另外:

/etc/crontab中是全局的定时任务配置

/var/log/cron可以查看定时任务的执行记录

/var/spool/cron/work中记录了work用户设置的定时任务

还有一点要注意:crontab是以non-login方式启动任务的,这时环境中会加载~/.bashrc的内容,不会加载~/.bash\_profile的内容。如果crontab运行的脚本中依赖 …

more ...

非root使用源码安装mysql

安装步骤:

  1. 从mysql官网下载源码。5.5.24源码地址
  2. 下载并安装cmake,用来生成makefile。2.8.9地址
  3. 解压mysql-5.5.24.tar.gz,进入解压后的目录
  4. 运行cmake -i
  5. 设置三个变量:CMAKE_INSTALL_PREFIX,MYSQL_DATADIR,SYSCONFDIR,其他默认即可。
  6. 这三个值默认是/usr/local/mysql作为根目录,可以设置为/home/$USER/mysql或者其他地址
  7. 或者直接运行cmake -D CMAKE_INSTALL_PREFIX=/$HOME/tools/mysql -D MYSQL_DATADIR=/$HOME/tools/mysql/data –D SYSCONFDIR=/$HOME/tools/mysql …
more ...

python MySQLdb的初步使用

MySQLdb库使用还是比较简单的,了解了几个API就可以开始捣腾了。

这里有一些API的说明

基本使用步骤:

  1. 导入MySQLdb库:import MySQLdb
  2. 建立一个DB连接:db_con = MySQLdb.connect(DB_host,DB_user,DB_password,DB_database)
  3. 执行命令:db_con.query(sql_cmd) 或者 cursor = db_con.cursor(); cursor.excute(sql_cmd); cursor.close()
  4. 获取返回值:db_con.store_result().fetch_row(),这个返回值是一个二元元组。比如要获取返回值的第一列第一行就是db_con.store_result().fetch_row()[0][0]
  5. 关闭连接:db_con.close()

有一点比较需要注意的是:

store_result()fetch_row()函数并不仅是返回某个值这么简单,而是有副作用的,调用一次之后会清除原来的结果 …

more ...

linux cp过程中ignore部分文件

遇到一个需求,在cp的过程中需要ignore掉部分文件,发现cp没有自带ignore之类的选项。

比较容易的方式是使用rsync,但是折腾的过程中发现,使用find+cp也是可以达到ignore的目的的:

cd src && find ./ -type f -not -name '12' -not -name '13' -exec cp --parents '{}' './dst/' \;

记录一下,免得需要时又到处找。

more ...

使用git合并多个提交

假设要合并最后的2个提交,可以按如下命令进行:

  1. git rebase –i HEAD\~2

运行完该命令,会出现如下所示内容: image

  1. 将第二个pick修改为squash或者s,然后输入":wq"退出。
  2. 这时git会自动第二个提交合并到第一个中去。并提示输入新的message(就是我们常说的comments),如下:
    image

  3. 编辑输入新的message,然后输入":wq"退出

  4. 此时本地的(HEAD中)最后两次提交已经被合并为一个。git log可以查看。
  5. 如果需要提交到远端,运行git push --force origin master即可。
more ...

linux会话中将前台任务转入后台

有时会遇到这样一种情况:在没有使用screen之类的管理会话的软件的时候,需要退出当前运行会话,但会话中有个任务运行了很久了,退出的话这个任务会被kill掉(收到SIGHUP的信号)。

下面的方法可以将这个前台进程转入后台,并指定这个任务不被发送SIGHUP信号:

  1. ctrl+z将任务暂停,这时屏幕会打印这样一句[1]+  Stopped  ./myserver
  2. 使用bg 1将这个任务转入后台运行(1是刚才暂停任务的job号,步骤1中屏幕提示方括号里面的内容,如果忘了也可以用jobs命令查看)
  3. 使用disown –h %1指定shell退出时不要发送SIGHUP给任务1(注意任务号前面有个%)

关于disown的详细说明,可以查看man bash或man disown搜索disown

当然,如果在启动任务之前意识到这个问题,可以用nohup ./myserver &的方式将会话放到后台运行,并通过nohup指定进程不处理SIGHUP信号。

more ...

C++编码优化之减少冗余拷贝或赋值

开始尝试写一些技术上的blog之后,发现一些好处,写的时候可以把一些零散的知识点梳理清楚,而且有空还可以回顾。

最近做了一些模块的性能优化工作,虽然基本都是一些编码上的优化和少量的设计优化,但是优化方式还是比较多且杂的,因此想写个编码优化的系列,把之前曾经遇到过的一些情况记录下来。

当然,在说到某个具体的优化手段时也有可能补充一些书本上介绍的优化方法。

=========背景介绍结束,正文开始=========

之所以说减少拷贝或赋值,而不是减少临时变量,因为有几个情况,并不是临时变量引入的。但目前看来,减少临时变量的产生确是减少冗余拷贝构造的一大途径。

因此,减少冗余拷贝构造准备先简单从两个方面来叙述:临时变量&&非临时变量

临时变量

目前遇到的一些产生临时变量的情况:函数实参、函数返回值、隐式类型转换、多余的拷贝

1. 函数实参

这点应该比较容易理解,函数参数,如果是实参传递的话,函数体里的修改并不会影响调用时传入的参数的值。那么函数体里操作的对象肯定是函数调用的过程中产生出来的。

那么这种情况我们该怎么办呢?

如果callee中确实要修改这个对象,但是caller又不想callee的修改影响到原来的值,那么这个临时变量就是必须的了,不需要也没办法避免。

如果callee中根本没有修改这个对象,或者callee中这个参数本身就是const型的,那么将实参传递改为引用传递是个不错的选择(如果是基本类型的函数实参,则没有必要改为引用),可以减少一个临时变量而且不会带来任何损失。

另外,推荐一个静态代码检查工具cppcheck …

more ...