《深入浅出c++11之5提高性能及操作硬件的能力》

5 提高性能及操作硬件的能力

5.1 常量表达式

5.1.1 运行时常量性与编译时常量性
const int i = 3; //运行时常量
constexpr int GetConst() {return 1;}; //编译时常量
5.1.2 常量表达式函数

常量表达式函数的要求:

  1. 函数体只有单一的return返回语句。(如果有static_assert, using和typedef指令通常也是可以的)
  2. 函数必须返回值(不能是void函数)
  3. 在使用前必须已有定义
  4. return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。

注意:constexpr不会对函数产生重写。

5.1.3 常量表达式值
//大多数情况下,如下语句没有区别。
//如果i在全局名字空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显示地使用了它的地址,编译器可以选择不为它生成数据,而仅将其当作编译时期的值(枚举值,也是光有名字,没有产生实际数据)
const int i = 1;
constexpr int j = 1;

//标准要求编译时的浮点常量表达式值的精度要至少等于(或者高于)运行时的浮点数常量的精度
constexpr float k = 1323.342;

//对于自定义类型的数据,需要自定义常量构造函数
struct MyType {
	constexpr MyType(int x): i(x) {}
	int i;
};

constexpr MyType mt = {0};

常量表达式的构造函数使用上的约束:

  1. 函数体必须为空
  2. 初始化列表只能由常量表达式赋值
//编译时期的运算
//实际运行的代码没有调用Fibonacci这个函数,数组的值已经被计算好了
constexpr int Fibonacci(int n) {
	return (n == 1) ? 1 : ((n == 2) ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2));
}

int main() {
	int fib[] = {
			Fibonacci(11), Fibonacci(12),
			Fibonacci(13), Fibonacci(14),
			Fibonacci(15), Fibonacci(16)
	};
	
	for (int i : fib) {
		cout << i << endl;
	}
}

//模版元编程,基于模版的编译时期运算
//该模版类定一个静态变量val,而val的定义方式是递归的。因此模版将会递归地进行推导
template <long num>
struct Fibonacci {
	static const long val = Fibonacci<num - 1>::val + Fibonacci<num - 2>::val;
};

template <>
struct Fibonacci<2> {
	static const long val = 1;
};

template <>
struct Fibonacci<1> {
	static const long val = 1;
};

template <>
struct Fibonacci<0> {
	static const long val = 0;
};

int main() {
	int fib[] = {
		Fibonacci<11>::val, Fibonacci<12>::val,
		Fibonacci<13>::val, Fibonacci<14>::val,
		Fibonacci<15>::val, Fibonacci<16>::val,
	};
	
	for (int i : fib) {
		cout << i << endl;
	}
}

5.2 变长模版

5.2.1 变长模版:模版参数包和函数参数包
//Elements为模版参数包
template <typename... Elements> class tuple;

//实现tuple模版,通过定义递归的模版偏特化,使得模版参数包在实例化时能够层层展开,直到参数包中的参数逐渐耗尽或达到某个数量的边界为止。
//参数包在基类描述列表中进行展开
template <typename Head, typename... Tail>
class tuple<Head, Tail...> : private tuple<Tail...> {
	Head head;
};

template<>
class tuple<> {}; //边界条件

//非类型的模版
template <long... nums> struct Multiply;

//参数包在表达式中进行展开
template<long first, long... last>
struct Multiply<first, last...> {
	static const long val = first * Multiply<last...>::val;
};

template<>
struct Multiply<> {
	static const long val =1;
};

int main() {
	cout << Multiply<2, 3, 4, 5>::val << endl; //120
	cout << Multiply<22, 44, 66, 88, 9>::val << endl; //50599296
}

对于变长模版函数,变长的函数参数也可以声明成函数包,如:

	//函数参数包必须唯一,且是函数的最后一个参数
	template<typename ...T>
	void f(T ... args);
5.2.2 变长模板:进阶

标准定义了以下7种参数包可以展开的位置:

  • 表达式
  • 初始化列表
  • 基类描述列表
  • 类成员初始化列表
  • 模板参数列表
  • 通用属性列表
  • lambda函数的捕捉列表
实例化T<X,Y>
注意下面两个包扩展的不同
//解包为 class T<X, Y>: private B<X>, private B<Y> {};
template <typename... A>
class T: private B<A>... {};

//解包为 class T<X, Y>: private B<X, Y> {};
template<typename... A>
class T: private B<A...> {}

C++11中,标准引入新操作符"sizeof…", 作用是计算参数包中的参数个数。

template <typename... A>
int Vaargs(A...args) {
	int size = sizeof...(A);
	switch (size) {
		case 0:
	}
	
	return size;
}

5.3 原子类型与原子操作

5.3.1 内存模型,顺序一致性与memory_order

实际默认情况下,在C++ 11中的原子类型的变量在线程中总是保持着顺序执行的特性(非原子类型则没有必要,因为不需要在线程间进行同步),这样的特性为“顺序一致”,即代码在线程中运行的顺序与程序员看到的代码顺序一致。

t = 1;
a = t;
b = 2;
上面代码的伪汇编代码为:
1: Loadi reg3, i; #将立即数1放入寄存器reg3
2: Move reg4, reg3; #将reg3的数据放入reg4
3: Store reg4, a; #将寄存器reg4的数据存入内存地址a
4: Loadi reg5, 2; #将立即数2放入寄存器reg5
5: Store reg5, b; #将寄存器reg5中的数据存入内存地址b
通常,指令总是按照1->2->3->4->5顺序执行,即内存模型为强顺序的,这种情况,a的赋值总是在b的赋值之前发生。
这里指令1,2,3和指令4,5运行顺序上毫无影响(使用了不同的寄存器,以及不同的内存地址),一些处理器就可能将指令执行的顺序打乱,按照1->4->2->5->3顺序执行(通常这样的执行顺序都是超标量的流水线,即一个时钟周期里发射多条指令而产生的)。这种顺序(b的赋值在a的赋值前完成),称为弱顺序。

如果要保证指令的顺序,通常需要在汇编指令中加入一条所谓的内存栅栏。即在指令3和4之间加入
7: Sync
该指令迫使已经进入流水线中的指令都完成后,才执行Sync以后的指令。

C++ 11中一共定义了7种memory_order的枚举值

枚举值定义规则
memory_order_relaxed不对执行顺序做任何保证
memory_order_acquire本线程中,所有后续的读操作必须在本条原子操作完成后执行
memory_order_release本线程中,所有之前的写操作完成后才能执行本条原子操作
memory_order_acq_rel同时包含memory_order_acquire和memory_order_release标记
memory_order_consume本线程中,所有后续的有关原子类型的操作,必须在本条原子操作完成之后执行
memory_order_seq_cst全部存取都按顺序执行 (C++ 11中都atomic原子操作默认行为)
  1. 原子存储操作可以使用
    memory_order_relaxed 、memory_order_release、memory_order_seq_cst
  2. 原子读取操作可以使用
    memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst
  3. RMW操作(read-modify-write),即一些需要同时读写的操作
    memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst
//只需要保证a.store先于b.store发生,b.load先于a.load发生
int Thread1(int) {
	int t = 1;
	a.store(t, memory_order_relaxed);
	b.store(2, memory_order_release); //本原子操作前所有的写原子操作必须完成
}

int Thread2(int) {
	while (b.load(memory_order_acquire) != 2); //本原子操作必须完成才能执行之后所有的读原子操作
	cout << a.load(memory_order_relaxed) << endl; //1
}

5.4 线程局部存储

线程局部存储(TLS,thread local storage),就是拥有线程生命期及线程可见性的变量。

堆空间、静态数据区(从可执行文件角度看,静态数据区对应的是可执行文件的data、bss段的数据,而从c/c++语言层面而言,则对应的是全局/静态变量)是线程间共享的。

//每个线程将拥有独立的errCode
__thread int errCode;
c++11
//int thread_local errCode;

5.5 快速退出:quick_exit与at_quick_exit

  1. abort函数不会调用任何的析构函数,而exit属于“正常退出”范畴的程序终止,exit函数会正常调用自动变量的析构函数,并且还会调用atexit注册的函数。
  2. C++ 11中,标准引入了quick_exit函数,该函数并不执行析构函数而只是使程序终止。而quick_exit与exit同属于正常退出。at_quick_exit注册的函数也可以在quick_exit的时候被调用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值