5.1 优化编译器的能力和局限性

gcc 优化等级:

-O0:这是默认的优化等级,基本上不进行任何优化,以便于调试。源代码会被尽可能地转化为目标代码,而不会对程序的执行速度进行优化。
-O1:这是一个适度的优化等级,它会在编译时执行一些简单的优化,如常量折叠、死代码删除、循环展开等,但不会影响编译时间太多。这个等级的优化通常不会显著改变程序的行为。
-O2:这是最常见的优化等级,它会执行更多的优化,包括函数内联、循环优化、寄存器分配等,可能会稍微增加编译时间,但是通常可以产生更快的可执行文件。
-O3:这是最高级别的优化,它包含了-O2 中的所有优化,并且会执行更激进的优化策略,如更多的内联、循环展开等。这可能会显著增加编译时间,但同时也可能产生执行效率最高的代码。
-Os:这个优化等级专注于减少输出文件的大小,同时仍然保持一定的运行时性能。它会使用一些特定的技巧来减小代码和数据的大小,适合用于嵌入式系统或资源有限的环境。
-Og:这个优化等级主要用于调试,它会生成更多的调试信息,同时尽量保持源代码和目标代码的一致性,以便于调试。
-Ofast:这是为了追求极致性能的优化等级,它会启用所有可用的优化,并且可能会违反 C/C++语言标准的一些规则,比如浮点运算的精度可能会有所损失。


两种指针可能指向同一个内存位置的情况称为内存别名使用

比如

void twiddle1(long *xp, long *yp)
{
	*xp += *yp;
	*xp += *yp;
}

void twiddle2(long *xp, long *yp)
{
	*xp = 2* *yp;
}

xpyp指针不指向同一地址时,twiddle1可以被优化成twiddle2,但如果xp == yp,那结果就不一样了,twiddle1该地址的值会变成 4 倍,而twiddle2只是两倍。所以编译器对这种情况不能进行优化。


提供一个 combine1 函数,看看我们能如何来优化它。

// for add
#define IDENT 0
#define OP +
// for multiply
#define INENT 1
#define OP *

void combine1(vec_ptr v, data_t *dest)
{
	long i;

	*dest = IDENT;
	for (i = 0; i < vec_length(v); i++) {
		data_t val;
		get_vec_element(v, i, &val);
		*dest = *dest OP val;
	}
}

int get_vec_element(vec_ptr v, long index, data_t *dest)
{
	if (index < 0 || index >= v->Len)
		return 0;
	*dest = v->data[index];
	return 1;
}

long vec_length(vec_ptr v)
{
	return v->len;
}

5.4 消除循环的低效率

识别要执行多次但是计算结果不会改变的操作,放到循环外面。

combine1 中的vec_length()可以放到循环外面。

void combine2(vec_ptr v, data_t *dest)
{
	long i;
	long length = vec_length(v);

	*dest = IDENT;
	for (i = 0; i < length; i++) {
		data_t val;
		get_vec_element(v, i, &val);
		*dest = *dest OP val;
	}
}

5.5 减少过程调用

过程调用会带来开销。get_vec_element不用每次循环都调用。

void combine3(vec_ptr v, data_t *dest)
{
	long i;
	long length = vec_length(v);
	data_t *data = get_vec_start(v);

	*dest = IDENT;
	for (i = 0; i < length; i++) {
		*dest = *dest OP data[i];
	}
}

5.6 消除不必要的内存引用

之前每次循环都会引用内存*dest,可以提供一个局部变量来存储。

void combine4(vec_ptr v, data_t *dest)
{
	long i;
	long length = vec_length(v);
	data_t *data = get_vec_start(v);
	data_t acc = IDENT;

	for (i = 0; i < length; i++) {
		acc = acc OP data[i];
	}
	*dest = acc;
}

5.7 理解现代处理器

// TODO:

5.8 循环展开

通过增加每次迭代计算的元素的数量,减少循环的迭代次数。

通过循环展开,

  1. 可以减少不直接有助于程序结果的操作的数量,例如循环索引计算和条件分支。
  2. 可以进一步变化代码,减少整个计算中关键路径上的操作数量。
void combine5(vec_ptr v, data_t *dest)
{
	long i;
	long length = vec_length(v);
	long limit = length-1;
	data_t *data = get_vec_start(v);
	data_t acc = IDENT;

	// combine two elements at a time
	for (i = 0; i < limit; i+=2) {
		acc = (acc OP data[i]) OP data[i+1];
	}
	// finish any remaining elements
	for (; i < length; i++) {
		acc = acc OP data[i];
	}
	*dest = acc;
}

GCC 优化等级达到 3 或更高时,就会自动执行循环展开。

5.9 提高并行性

// TODO: