C++ 应用程序性能优化
eryar@163.com
1. Introduction
对于几何造型内核OpenCASCADE,由于会涉及到大量的数值算法,如矩阵相关计算,微积分,Newton迭代法解方程,以及非线性优化的一些算法,如BFGS,FRPR,PSO等等用于多元函数的极值求解,所以这些数值算法的性能直接影响系统的性能。软件的性能优化是计算机软件开发过程中需要一直关注的重要因素,因此有必要学习下C++应用程序性能优化的方法。
在网上寻找相关资料时,发现这方面的资料也很少,最后发现一本由电子工业出版社出版的《C++应用程序性能优化方法》,从中可以学习下IBM的性能优化方法。
本文主要结合《C++性能优化方法》并结合代码实例来说明内存优化处理对程序性能的影响。看完本书,其实发现C++性能优化方法主要还是依赖的计算机相关的基础知识,比如说计算机操作系统,数据结构与算法等等。
2.Memory Optimize
C++程序中的存储空间可以分为静态/全局存储区,栈区和堆区。静态/全局存储区和栈区的大小一般在程序编译阶段决定;而堆区则随着程序的运行而动态变化,每一次程序运行都会有不同的行为。这种动态的内存管理对于一个程序在运行过程中占用的内存大小及程序的性能有非常重要的影响。
因为静态/全局存储区在编译时就已经确定了,而栈区则是由操作系统在管理,这些都不劳程序员费神。就是这个堆区是提供给程序员的自由舞台,可以任性发挥。堆区的存储区域怎么玩呢?理解了这三个问题应该就知道了:一是堆区数据怎么产生(从哪儿来)?二是堆区的数据怎么销毁(到哪儿去)?堆区数据怎么访问?由C++标准规定得知,C++实现通过全局的new和delete来提供动态内存的访问和管理。而堆区数据的访问就是通过指针了。当使用new/delete来操作堆上的存储区域时,操作系统就要对堆的存储区域进行管理,所以这个管理工作就会对应用程序的性能有影响。
为了解决内存泄露问题,即new之后不再使用时并没有delete,OpenCASCADE中入了Handle智能指针的宏定义。Handle的使用也很简单,只需要将用Handle(Class)就可以了。
利用默认的内存管理new/delete在堆上分配和释放内存会有一些额外的开销。系统在接收到一定大小内存请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块等)找到合适大小的内存块。如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块,然后系统更新内存块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。
默认的内存管理函数还考虑到多线程的应用,需要在每次分配和释放内存时加锁,同样增加开销。可见,如果应用程序频繁地在堆上分配格释放内存,则会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率。
由此可见,在简单的new和delete背后,系统默默地为我们做了这么多的事,而做这些事都是要花时间的啊!虽然默认的内存管理算法也考虑了性能,但是考虑的是更通用的情况,为了应付更复杂、更广泛的情况,需要做更多额外工作。而对于具体的应用程序来说,适合自身特定的内存管理则可以获得更好的性能,为此OpenCASCADE引入了自己的内存管理机制,与内存池概念类似。OCCT的内存通过配置,可以使用没的优化技术,或者不使用任何优化直接使用系统的malloc和free。这些配置都是通过环境变量来实现,其中打开和关闭内存优化的开关是环境变量:MMGT_OPT,这个默认是0,即不使用任何优化;设置成1就是使用;设置成2就是使用TBB的内存优化技术(这个的前提是第三方库正确配置,如果没有还是使用的malloc和free;
// paralleling with Intel TBB
#ifdef HAVE_TBB
#include <tbb/scalable_allocator.h>
using namespace tbb;
#else
#define scalable_malloc malloc
#define scalable_calloc calloc
#define scalable_realloc realloc
#define scalable_free free
#endif
下面通过代码来看看使用内存管理的性能有什么变化。
3.Code Example
下面通过程序来看看使用这些技术对性能的影响:
/*
* Copyright (c) 2016 Shing Liu All Rights Reserved.
*
* File : main.cpp
* Author : Shing Liu(eryar@163.com)
* Date : 2016-07-31 11:54
* Version : OpenCASCADE7.0.0
*
* Description : Test OCCT Memory Manager and Handle(smart pointer).
*/
#include <OSD_Timer.hxx>
#include <Poly.hxx>
#include <Poly_Triangulation.hxx>
#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")
/*
*@brief test memory without Handle(smart pointer) management.
*
*/
void testMemory(Standard_Integer theCount)
{
OSD_Timer aTimer;
aTimer.Start();
for (Standard_Integer i = 0; i < theCount; i++)
{
Poly_Triangulation* aTriangulation = new Poly_Triangulation(10, 5, Standard_False);
delete aTriangulation;
}
aTimer.Stop();
aTimer.Show();
}
/*
*@brief test memory with Handle(smart pointer) management.
*
*/
void testHandle(Standard_Integer theCount)
{
OSD_Timer aTimer;
aTimer.Start();
for (Standard_Integer i = 0; i < theCount; i++)
{
Handle(Poly_Triangulation) aTriangulation = new Poly_Triangulation(10, 5, Standard_False);
}
aTimer.Stop();
aTimer.Show();
}
/*
* @brief set environment variable MMGT_OPT=0 to use malloc/free directly;
* set environment varialbe MMGT_OPT=1 to use OCCT memory optimization technique;
* set environment variable MMGT_OPT=2 to use paralleling with Interl TBB;
*/
int main(int argc, char* argv[])
{
int aCount = 100000;
std::cout << "\ntest pointer without handle" << std::endl;
testMemory(aCount);
std::cout << "\ntest pointer with handle" << std::endl;
testHandle(aCount);
return 0;
}
如果直接运行上面的代码编译的程序,得到的结果如下图所示:
由图可知,当使用Handle(智能指针)的时候,时间上影响不大,即使用Handle对性能影响基本上可以忽略,但是得到很多好处,主要的就是不用自己去delete了。使用Visual Studio的性能分析工具查看,结果也类似:
时间的开销也是集中在内存的分配上面:
注意到上面的Allocate()是类Standard_MMgrRaw的,即是直接使用的系统的malloc和free来管理内存。下面设置环境变量MMGT_OPT=1来使用OCCT的内存优化类看看对性能影响如何?
程序运行的结果如下图所示:
与没有使用内存优化的时候0.1相比,使用了内存优化处理的要快40%左右。
由下图可以看出,性能热点也是集中在内存的分配上面:
注意现在内存分配使用的是Standard_MMgrOpt类中的Allocate函数:
总的来说,将环境变量MMGT_OPT设置成1来使用OCCT的内存优化算法,性能提升还是很明显的。
4.Conclusion
当程序规模越来越大,算法越来算复杂的时候,找到性能的瓶颈越麻烦一些。性能优化第一步是测量,找到合适的测量工具。《C++应用程序性能优化》一书中提供的是IBM Rational Quantify,在网上搜了下还有Intel VTune Amplifier等,功能都很强大。在Windows中开发程序使用的Visual Studio自带了性能分析功能,使用起来也比较方便。
找到性能瓶颈,就要对其进行分析原因,进而修改程序,提高性能。这方面的方法论可以借鉴《C++应用程序性能优化》,从数据结构、程序启动、内存管理等方面来分析。摘出此书中程序性能优化的流程图:
5. References
1.冯宏华,徐莹,程远,汪磊. C++应用程序性能优化. 电子工业出版社.
2.Scott Meryers. Effective C++(评注版). 电子工业出版社. 2011
3.OpenCASCADE Foundation Classes Document 7.0.0. 2016