12 More Effective C++—条款16/17 (2/8原理与延缓求值)

1 “2/8”原理

二八原理指一件事情的20%需要投入80%的精力来做,即要分清主次点。这种情况在程序编写的时候尤为突出。关键性能点、重要逻辑代码一般都是集中在小部分区域,而这部分区域需要我们特别关注。

在排查程序的新能瓶颈时,我们需要借助工具:profiler——程序分析器。比较知名的有Google profiler,其可以检测程序的运行时间和内存消耗情况。

我们要使用可重现的测试用例对程序进行测试,否则无法知道程序瓶颈、问题到底出在什么地方。

2 延缓求值

“延缓求值”指只有在真正用到该值的时候,我们再计算出所需要的数值。常常代码的组合逻辑与程序的运行逻辑不相符。如下面的代码,虽然所有的值都在函数的开头声明,但有些值在程序后面才会用到。

void func() {
	int a = geta(), b = getb();
	double c = getc(), d = getd();
	// do something0
	process(a);
	// do something1
	process(b);
	// do something2
	process(c);
}

1 引用计数 (reference-count)

字符串s1赋值给s2时,有如下两种方案。方案1被称作eager evaluate(立即求值), 方案2被称作lazy evalute(延缓求值)。由于方案1在每次赋值的时候都会复制一遍内容,无论s2是否用得到,都会造成极大开销。

1,直接复制s1中的内容给s2
2,s2只是指向s1, 只有对s2修改时,才复制s1中的内容。

引用计数的情况时,若多个对象内容相同,则共享一个副本。一旦某个对象内容发生变化,则复制并修改对应的副本。这样做节省内存,且提高了程序的运行效率——只有在需要的时候,带来复制数据的开销。

引用计数不仅可以降低数据的复制次数,后面的文章将会详细介绍,使用该技术管理动态分配内存。其中使用的例子是“共享智能指针shared_ptr”。

2 区分读写

,进一步发现,如果只是修改一个长字符串中的一个字符,是否需要复制整个字符串。
如下面代码所示。在“引用计数”的基础上,读取s2中的一个字符代价很小——可能采用引用计数。但如果修改s2中的一个字符,可能会复制整个字符串,开销较大。由此提出两个问题:

1,是否可以区分读写操作?
2,写操作是否可以不复制整个字符串?

string s1 = "hello hello hello hello hello", s2 = s1; // 一个长字符串,“逗号操作符”保证了求值顺序是从左向右
std::cout << s2[0];
s2[3] = 'd';

3 延缓访问 (lazy fetch)

数据库中某个表包含若干行数据,每行数据对应一个下面代码所示的对象。每个对象包含大量字段,如果一次性读入一行,创建一个对象,将造成巨大开销。

class LargeObject {
	public:
		LargeObject(ObjectId id);
		std::list<std::string> m_fieldList;
}

现在我们采用“延迟”思想,每次需要构建表中一行数据对象时,只需要创建一个“空壳”,只有到真正需要数据时,再从数据库中读取某个字段的数据。从而加速“单次响应”。

这种做法被称作“on-demand”做法。

“on-demand”技术被应用于计算机系统中的各种场景,比如从硬盘中读取数据、计算数据等。其实质是将一次大任务量、耗时比较长的请求,拆解成多个小请求,使只是根据当前需要进行数据访问或计算。需要注意,所有小请求的开销总和 >= 一次总的请求的开销。

比如日常生活中,陡坡比较难爬,而缓坡比较容易。但是上升同样的高度,缓坡需要走更长的距离,做更多的功,消耗更多的能量。

4 表达式延迟求值

本文最后一种“on-demand”技术的应用是,推迟一些复杂的计算。计算两个1000 * 1000矩阵的乘法将会消耗大量资源:计算资源,存储资源。但是我们可能只需要计算结果的一小部分数据。那么对其他数据的计算与存储都做了无用功。

因此,可以采用下面方式推迟计算:

1,直到真正需要访问某个数据时,才进行复杂的计算。
2,减少赋值操作,只有在数据真正需要的地方,再去访问最后的计算结果。

通常,计算软件会给出这些方面的优化,比如matlab, APL等。

3 C++语言特性知识——mutable

mutable关键字用于修饰类字段,使这些字段可以在const函数中被修改。如下面所示:

class MutableClass {
	public:
		void readButModify() const; // 如果不加mutable修饰,m_member只能被读取,不能在本函数中修改
	private:
		mutable int m_member;
}
void MutableClass::readButModify() {
	m_member = 0;
}

猜你喜欢

转载自blog.csdn.net/zhizifengxiang/article/details/83573362