more effective C++ 效率解析

本文探讨了提高C++程序效率的方法,包括遵循80-20法则、使用程序分析器定位瓶颈,以及介绍了四种Lazy evaluation的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提升程序的效率

作为c++程序员,要提升程序的效率,那么主要是从两个方面来考量:

1.与程序语言无关的一些法则。

2.与C++密切相关的一些效率法则。(不要产生和销毁过多的对象)。


与程序语言无关的一些法则:

1.牢记80-20法则,一个程序80%的资源用于20%的代码上,我们必须有针对性的改进这个20%的代码才能提升我们代码的效率。具体针对哪部分进行优化,并不是靠猜测的,而是使用程序分析器(profiler)。具体看我们的代码在哪里消耗大量的时间。

不过,话虽然这样说,但实际操作起来,效率优化并不是件容易的事。时间复杂度是最容易拉开效率差距的地方,但却也是最难拉开人与人之间差距的地方——毕竟很多问题的解决方案都比较成熟了,要能找到个时间复杂度更优的算法似乎不是一件容易的事情。然而,即便是复杂度相同的两份程序,由于程序常数不同,运行效率也往往有很大差异。试一下stdlib.h里的qsort,以及STL里的sort,就可以清楚地看出同样O(NlogN)的排序算法能有多大区别。不仅如此,实际程序往往还会涉及I/O、线程通信等操作,这些操作的快慢可不是靠复杂度分析就能得出结论的了。(vs用于自己的代码分析工具)

要注意一点的是,写程序时最好不要没做分析就过早地进行“性能优化”,正如上文所提到的,虽然有人提到Hash表和I/O操作会影响性能,但从性能分析的结果来看却非如此。这两者所带来的时间开销非常少。如果不经分析就盲目优化,也许只会事倍功半。

      另外还有一点要注意的是,虽然性能分析工具指明了程序各个部分的耗时,但这也不意味我们改进效率一定要优先改进耗时最多的部分。固然,改进耗时最多的部分往往能得到最明显的效果,但这并不意味耗时最多的部分很容易改进。像上文所示的Split函数,虽然其耗时并非最多,但由于其改进非常简单,有时反而会成为我们优先改进的对象。在实际项目中,我们要在改进所能得到的效果以及改进所要投入的精力之间妥协,优先完成有能力做而效果又比较明显的性能优化。

考虑使用Lazy evaluation(缓式评估)其实就是拖延战术,就是到了不得不做的时候才去做

这对于人生并不是好建议,但是对于程序有时真的是一个好的建议。在撰写某种方式的classes,让他们延缓运算,直到那些运算结果刻不容缓的被迫切需要为止。

Lazy evaluation的四个主要用途

1.Reference counting(引用计数)

举一个例子:

class  String {...};//一个字符串类。
string s1="Hello";
string s2=s1;
cout<<s1;
cout<<s2+s1;
s2.convertToUpperCase();
在这个例子中,我们本来在string s2=s1时就需要给s2构建一个副本,但是实际上,由于后面两句cout都是只读的,所以我们可以延缓对s2的复制操作,直到

convertToUppercase()我们才执行复制操作。如果最后我们的s2并没有实际的修改代码,那么s1以及s2就可以一直共享副本,避免可复制操作提升了效率。

(是否是一种写时复制的手法)

2.区分读和写
用代码来解释:

String s="Homer's Iliad";//是一个reference counted 字符串可能与其他字符串共享着副本。
...
cout<<s[2];//是对s的读操作,没有必要进行复制出副本。
s[2]='x';//是对s的写操作,有必要制造出副本来了

显然同样是operator [] 操作,我们需要区分出读还是写,那么具体的实现可以一个代理class (proxy class来完成)(相当于增加了一层封装),以此来延缓决定究竟是读还是写操作。

3.缓式取出(Lazy Fetching)

用代码来解释:

 class LargeObject { //存储于数据库中的大型的,可持久存在的对象
public:
	LargeObject(ObjectID id);//连接数据库,从数据库中取出。
	
	const string& field1() const; 取出各个字段的值。
	int field2() const;
	double field3();
};
假设一个处理函数:

void restoreAndProcessObject(ObjectID id)
{
	LargeObject object(id);
	if(object.field2()==0)
	{
		cout<<"object"<<id<<":null field2.\n";
	}
}

这里我们会查询一个字段,就得连接数据库得到这个大型对象。好的做法是产生对象的时候建立一个空壳对象,在实际读取一个字段的时候,在读取相应字段

class LargeObject
{
public:
	LargeObject(Object id);
	...
private:
	 ObjectID oid;
	mutable string *field1value;
	mutable string *field2value;
}
LatrgeObject::LargeObject(ObjectID id)
:oid(id),fieldValue(0),field2Value(0),field3Value(0),...
{
	
}
const string & LargeObject::field1() const
{
	if(field1Value==NULL)
	{
	//读取相应字段的值
	}
	return *field1Value;
}
其实这种方式跟单例模式是极为相似的。


4.表达式缓评估

代码解释:

template <class T>
class Matrix{..};
Matrix<int> m1(1000,1000);
Matrix<int> m2(1000,1000);

Matrix <int> m3=m1+m2;
显然这里还没有用到m3之前,就使用这个1000X1000的加法显然不划算,实际上我们最后根本不需要这种结果,并且可能只需要部分结果。这种不计算,当然需要维护一定的数据结构。

注意的是:Lazy evaluation 只有在你的软件被要求执行某些运算,而那些运算本来是可以避免的,lazy evaluation 才有用处。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值