提高C++性能的编程技术笔记:跟踪实例+测试代码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fengbingchun/article/details/83449625

当提高性能时,我们必须记住以下几点:

(1). 内存不是无限大的。虚拟内存系统使得内存看起来是无限的,而事实上并非如此。

(2). 内存访问开销不是均衡的。对缓存、主内存和磁盘的访问开销不在同一个数量级之上。

(3). 我们的程序没有专用的CPU,只能间歇地获得一个时间片。

(4). 在一台单处理器的计算机上,并行的线程并不是真正地并行执行,它们是轮询的。

“性能”可以有几种衡量标准,最常见的两种是空间效率和时间效率。空间效率标准寻求占用最小内存的软件解决方案。相似的,时间效率标准寻求占用最少处理器周期的解决方案。时间效率通常以响应时间和吞吐量来作为衡量标准。其它衡量标准还有编译时间和可执行文件的大小。

许多C++程序员在跟踪代码时通常的做法是,定义一个简单的Trace类将诊断信息打印到日志文件中。程序员可以在每个想要跟踪的函数中定义一个Trace对象,在函数的入口和出口Trace类可以分别写一条信息。尽管Trace对象将增加程序额外的执行开销,但是它能够帮助程序员找出问题而无须使用调试器。

最理想的跟踪性能优化的方法应该能够完全消除性能开销,即把跟踪调用嵌入在#ifdef块内。使用这种方法的不足在于必须重新编译程序来打开或关闭跟踪。还有一种选择:可以通过与正在运行的程序通信来动态地控制跟踪。Trace类能够在记录任何跟踪信息之前先检查跟踪状态。

影响C++性能的因素:I/O的开销是高昂的;函数调用的开销是要考虑的一个因素,因此我们应该将短小的、频繁调用的函数内联;复制对象的开销是高昂的,最好选择传递引用,而不是传递值。

内联对大块头函数的影响是无足轻重的。只有在针对那些调用和返回开销占全部开销的绝大部分的小型函数时,内联对性能的改善才有较大的影响。内联消除了常被使用的小函数调用所产生的函数开销。完美适合内联的函数就恰好是非常不适合跟踪的。

对象定义会触发隐形地执行构造函数和析构函数。我们称其为”隐性执行”而不是”隐性开销”是因为对象的构造和销毁并不总是意味产生开销。

通过引用传递对象还是不能保证良好的性能,所以避免对象的复制的确有利于提高性能,但是如果我们不必一开始就创建和销毁该对象的话,这种处理方式将更有利于性能的提升。

在完成同样的简单工作时,char指针有时可以比string对象更有效率。

以下是测试代码(the_tracing_war_story.cpp),分别有Trace1,Trace2,Trace3三个简单的类,它们的性能逐渐提高:

#include "the_tracing_war_story.hpp"
#include <string>
#include <iostream>
#include <chrono>

namespace tracing_war_story_ {
//////////////////////////////////////////////////
// 较差的Trace1类设计
class Trace1 {
public:
	Trace1(const std::string& name);
	~Trace1();
	void debug(const std::string& msg);

	static bool traceIsActive;
private:
	std::string theFunctionName;
};

inline Trace1::Trace1(const std::string& name) : theFunctionName(name)
{
	if (traceIsActive) {
		std::cout << "Enter function: " << name << std::endl;
	}
}

inline Trace1::~Trace1()
{
	if (traceIsActive) {
		std::cout <<"Exit function: " << theFunctionName << std::endl;
	}
}

inline void Trace1::debug(const std::string& msg)
{
	if (traceIsActive) {
		std::cout << msg << std::endl;
	}
}

bool Trace1::traceIsActive = false; // 默认设置为false关闭跟踪;可以在test_tracing_war_story中设置是否开启还是关闭跟踪

int addOne0(int x)
{
	return x+1;
}

int addOne1(int x)
{
	std::string name = "addOne1";
	Trace1 t(name);

	return x+1;
}

///////////////////////////////////////////////////
// Trace2类是对Trace1类的改进:将函数参数string类型调整为const char*
class Trace2 {
public:
	Trace2(const char* name);
	~Trace2();
	void debug(const char* msg);

	static bool traceIsActive;
private:
	std::string theFunctionName;
};

inline Trace2::Trace2(const char* name) : theFunctionName(name)
{
	if (traceIsActive) {
		std::cout << "Enter function: " << name << std::endl;
	}
}

inline Trace2::~Trace2()
{
	if (traceIsActive) {
		std::cout <<"Exit function: " << theFunctionName << std::endl;
	}
}

inline void Trace2::debug(const char* msg)
{
	if (traceIsActive) {
		std::cout << msg << std::endl;
	}
}

bool Trace2::traceIsActive = false; // 默认设置为false关闭跟踪;可以在test_tracing_war_story中设置是否开启还是关闭跟踪

int addOne2(int x)
{
	char* name = "addOne2";
	Trace2 t(name);

	return x+1;
}

/////////////////////////////////////////////////////
// Trace3类是对Trace2类的改进:消除包含在Trace2类内的string成员对象的无条件的创建
class Trace3 {
public:
	Trace3(const char* name);
	~Trace3();
	void debug(const char* msg);

	static bool traceIsActive;
private:
	// string指针可以把string对象的创建推迟到确定跟踪处于打开状态以后
	std::string* theFunctionName;
};

inline Trace3::Trace3(const char* name) : theFunctionName(nullptr)
{
	if (traceIsActive) {
		std::cout << "Enter function: " << name << std::endl;
		theFunctionName = new std::string(name);
	}
}

inline Trace3::~Trace3()
{
	if (traceIsActive) {
		std::cout <<"Exit function: " << theFunctionName << std::endl;
		delete theFunctionName;
	}
}

inline void Trace3::debug(const char* msg)
{
	if (traceIsActive) {
		std::cout << msg << std::endl;
	}
}

bool Trace3::traceIsActive = false; // 默认设置为false关闭跟踪;可以在test_tracing_war_story中设置是否开启还是关闭跟踪

int addOne3(int x)
{
	char* name = "addOne3";
	Trace3 t(name);

	return x+1;
}

int test_tracing_war_story()
{
	Trace1::traceIsActive = false;

	/*
	  Trace1实现就是一个无用对象对性能带来破坏性影响的实例:即创建和后面的销毁预计要使用却没有使用的不必要的对象
	  在关闭跟踪情况下产生的开销:
	  引发一系列计算:
	    (1). 创建一个作用域为test_tracing_war_story的string型变量name
	    (2). 调用Trace1的构造函数
	    (3). Trace1的构造函数调用string的构造函数来创建一个string
	  在此函数的结尾,Trace1对象和两个string对象被销毁:
	    (1). 销毁string型变量name
	    (2). 调用Trace1的析构函数
	    (3). Trace1的析构函数为成员string调用string的析构函数
	  在跟踪被关闭的情况下,string的成员对象从未被使用,Trace1对象本身也未被使用,所有这些用于对象的创建和销毁的计算都是纯碎的浪费
	*/
	std::string name = "test_tracing_war_story";
	Trace1 t(name);
	std::string moreInfo = "more interesting info";
	t.debug(moreInfo);

	using namespace std::chrono;
	high_resolution_clock::time_point timeStart, timeEnd;
	int count = 1000000;
	
	// 通过addOne0和addOne1测试Trace1对象的性能开销
	timeStart = high_resolution_clock::now();
	for (int i = 0; i < count; ++i) {
		addOne0(i);
	}
	timeEnd = high_resolution_clock::now();
	std::cout<< "addOne0 time spen: " <<(duration_cast<duration<double>>(timeEnd-timeStart)).count()<<" seconds"<<std::endl;
	
	timeStart = high_resolution_clock::now();
	for (int i = 0; i < count; ++i) {
		addOne1(i);
	}
	timeEnd = high_resolution_clock::now();
	std::cout<< "addOne1 time spen: " <<(duration_cast<duration<double>>(timeEnd-timeStart)).count()<<" seconds"<<std::endl;

	Trace2::traceIsActive = false;

	// 通过addOne2测试Trace2对象的性能开销
	timeStart = high_resolution_clock::now();
	for (int i = 0; i < count; ++i) {
		addOne2(i);
	}
	timeEnd = high_resolution_clock::now();
	std::cout<< "addOne2 time spen: " <<(duration_cast<duration<double>>(timeEnd-timeStart)).count()<<" seconds"<<std::endl;
	
	Trace3::traceIsActive = false;

	// 通过addOne3测试Trace3对象的性能开销
	timeStart = high_resolution_clock::now();
	for (int i = 0; i < count; ++i) {
		addOne3(i);
	}
	timeEnd = high_resolution_clock::now();
	std::cout<< "addOne3 time spen: " <<(duration_cast<duration<double>>(timeEnd-timeStart)).count()<<" seconds"<<std::endl;
	
	return 0;
}

} // namespace tracing_war_story

执行结果如下:

GitHubhttps://github.com/fengbingchun/Messy_Test

猜你喜欢

转载自blog.csdn.net/fengbingchun/article/details/83449625
今日推荐