【C++新特性】| 【05】提高性能及操作硬件的能力(constexpr、变长模板、原子类型、线程局部存储)

1、常量表达式

1.1 运行时常量
使用const关键字描述的都是一些运行时常量性;,即运行时数据不可改变;
以下是不通过编译:以下使用到的是编译器常量,故将不可以使用const;

在这里插入图片描述

1.2 编译时常量
C++11提供constexpr关键字,该关键字是编译时期的常量;
上述代码中,若将const转换为constexpr将会得到解决;

【注意】:
- 不能将该关键字作用于virtual成员函数上;
- 

常量表达式函数

满足条件:
- 函数体只有单一的return语句;
	- 特例可以在return前使用static_assert()、using、typedef等;
- 函数必须有返回值,不能为void;
- 在使用前必须已有定义(由于在编译时需要将其计算);
- return表达式中,不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式;
	- 表达式中不能包含运行时才能确定的值;
	- 不能出现赋值;
	- const int f() { return 1; }  === > constexpr int g() { return e(); }

常量表达式值

当该变量被constexpr所修饰,则该变量为常量表达式值,其必须被一个常量表达式赋值;

当`constexpr int j = 1`此类变量位于全局中,若代码中没有显示地使用它的地址,可以不为他生成数据;
【浮点常量】:
- 要求编译时的浮点常量表达式精度至少要>=运行时的浮点数常量的精度;

常量构造函数

【常量表达式的构造函数】:当常量表达式修饰自定义类型不能通过编译时
- 将定义一个常量构造函数即可;
struct MyType {
    constexpr MyType(int x) : i(x){}
    int i;
};

constexpr MyType mt = {0};

定义该构造函数的约束:
- 函数体必须为空;
- 初始化列表只能由常量表达式来赋值;

其他应用

常量表达式可以用于函数模板;
- 当该函数的实例化结果不满足常量表达式的需求,则constexpr将会被自动忽略;

【注意】:
- 并不是使用constexpr,编译器就一定会对其在编译期进行优化计算;

2、变长模板

2.1 变长函数
在C中就具有函数变长参数特性;
va_list:传入参数,辅助获取参数;
va_start:对参数进行初始化;
va_arg:从参数列表一个一个取出;

【注意】:
- 函数本身无法知道参数类型以及参数的数量;
- 对于没有定义转移字的非POD,使用变长函数会导致未定义;
int SumOfFloat(int cnt, ...) {
    va_list ap;
    int sum = 0;
    va_start(ap, cnt);
    for (int i = 0; i < cnt; ++i) {
        sum += va_arg(ap, int);
    }
    va_end(ap);
    return sum;
}

int main() {
    cout << SumOfFloat(3, 12, 10, 1) << endl;
    return 0;
}
2.2 变长模板参数

STL【tuple】| tuple源码刨析,为什么能传入自定义个数参数?

template<typename... Elements>class tuple;
其中Elements为模板参数包,tupple可以接收任意参数;
// 上述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;
};

void test() {
    cout << Multiply<1, 2, 3>::val << endl; // 6
}

案例:C++11实现printf函数

void Printf(const char* s) {
    while (*s) {
        if(*s == '%' && *++s != '%') {
            throw runtime_error("invalid format string: missing argument");
        }
        cout << *s++;
    }
}

template<typename T, typename... Args>
void Printf(const char* s, T value, Args... args) {
    while (*s) {
        if(*s == '%' && *++s != '%') {
            cout << value;
            return Printf(++s, args...);
        }
        cout << *s++;
    }
    throw runtime_error("extra arguments provided to Printf");
}

void test() {
    Printf("%s, %d", "word", 12);
}
2.3 进阶
C++11中,在以下位置可以展开参数包:
- 表达式;
- 初始化列表;
- 基类描述列表;
- 类成员初始化列表;
- 模板参数列表;
- 通用属性列表;
- lambda函数的捕捉列表;
// 多重继承
template<typename... A> class T:private B<A>... {};
解包==> 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> {};

sizeof…

可以计算参数包中的参数个数;
==> sizeof...(A);  // 获取参数包中A的个数

两个模板参数包的使用

template<typename A, typename B>
struct S {
    // 构造函数
    S() {
        cout << "A, B" << endl;
    }
};

template<
        template<typename... > class T, typename... TArgs,
        template<typename... > class U, typename... UArgs
        >
        struct S<
                T <TArgs... >,
                U <UArgs... >
                >{
            // 构造函数
            S() {
                cout << "Args..." << endl;
            }
        };

void test() {
    S<int, double> a;
    S<tuple<char, int>, tuple<int, char>> s;
}

变长模板和完美转发

struct A {
    A() {}
    A(const A& a) {
        cout << "Copy" << __func__  << endl;
    }
    A(A&& a) {
        cout << "Move" << __func__  << endl;
    }
};

struct B {
    B() {}
    B(const B& b) {
        cout << "Copy" << __func__  << endl;
    }
    B(B&& b) {
        cout << "Move" << __func__  << endl;
    }
};

template<typename... T>
struct MultiTypes;

template<typename T1, typename... T>
struct MultiTypes<T1, T...> : public MultiTypes<T...> {
    T1 t1;
    MultiTypes<T1, T...>(T1 a, T... b) : t1(a), MultiTypes<T...>(b...) {
        cout << "MultiTypes" << endl;
    }
};

template<> struct MultiTypes<> {
    MultiTypes<>() {
        cout << "MultiTypes<>()" << endl;
    }
};

template<
    template<typename...>
        class VariadicType,
    typename... Args
>
VariadicType<Args...> Build(Args&&... args) {
    return VariadicType<Args...>(std::forward<Args>(args)...);
}

void test() {
    A a;
    B b;
    Build<MultiTypes>(a, b);
}

3、原子类型与原子操作

3.1 原子操作与C++11原子类型
原子操作:多线程中最小的不可并行化的操作,故在多线程下,只有一个线程对其进行操作(不会出现例外);
	- 通过互斥的访问来保证;
	- 不需要通过加锁去锁的繁杂操作;

用法:
- atomic<T> t ==> 可以通过T来指定任意类型;
- 原子类型只能从其模板参数类型中构造,不允许原子类型进行拷贝、移动构造;
- 可以隐式转换T类型;


【atomic_flag】:该类型为无锁的,可以用该类型来实现一个自旋锁;
#include <atomic>
#include <thead>
#include <iostream>

using namespace std; 
// 测试案例
atomic_llong total{0};	// 原子数据类型

void func(int) {
    for (long long i = 0; i < 100000000LL; ++i) {
        total += i;
    }
}

void test() {
    thread t1(func, 0);
    thread t2(func, 0);

    t1.join();
    t2.join();

    cout << total << endl;
}

void test2() {
	atomic<int> a;
    int b = a;      // ==> int b = a.load();
    a = 1;          // ==> a.store(1)
}

atomic_flag实现自旋锁

atomic_flag lock = ATOMIC_FLAG_INIT;	// 设置为false状态

void f(int n){
	// test_and_set会不断设置新值并返回旧值
    while (lock.test_and_set(std::memory_order_acquire))	
        cout << "Waiting from thread " << n << endl;
    cout << "Thread" << n << " starts working" << endl;
}

void g(int n) {
    cout << "Thread " << n << " is going to start" << endl;
    lock.clear();			// 将lock设置false
    cout << "Thread " << n << " starts working" << endl;
}

void test() {
    lock.test_and_set();
    thread t1(f, 1);
    thread t2(g, 2);

    t1.join();
    sleep(5);
    t2.join();
}
// 当t1执行后,lock为true,则一直自旋等待,直到t2加入后,clear将其设置为false,t1才终止自旋,执行后续代码;

在这里插入图片描述

3.2 内存模型
【硬件内存模型】
当执行按照一定的顺序执行,则该内存模型为强顺序;
当执行不一定按照顺序执行,则该模型为弱顺序;
在多线程中,当线程间的内存数据被改变的顺序与机器指令中声明的不一致,则为弱顺序;
而上述的原子操作时强顺序的;

- 弱顺序的指令执行性能一般较高,当没有要求需要顺序执行时,使用该方式可以给程序提高性能;

【C++内存模型】
- 编译器保证原子操作间的指令顺序不变;
- 处理器对原子操作的汇编指令的执行顺序不变;
部分平台会阻止编译器优化,加入内存栅栏来保证atomic的顺序一致性,将会大大影响性能;

【如何解决不阻止编译器优化】:让程序为原子操作指定内存顺序(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	:全部存取都按顺序执行(可能会导致性能损失);

- 上述中,可以使用memory_order_release和memory_order_consume产生;
atomic<int> a;
atomic<int> b;

int func1(int) {
    int t = 1;
    a.store(t, std::memory_order_relaxed);
    b.store(2, std::memory_order_release);  // 本原子操作前,所有的写原子操作必须完成
}

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

void test() {
    thread t1(func1, 0);
    thread t2(func2, 0);

    t1.join();
    t2.join();
}

4、线程局部存储

即拥有线程生命周期及线程可见性的变量(单线程中的全局/静态变量被用到多线程中被线程共享);

当一个全局变量被用于多线程中时,该变量在线程间共享,总是会导致只能记录其中一个线程的数据,故为每个线程指派一个全局的变量(TLS);
C++11通过thread_local修饰符变量,在线程开始时被初始化,结束时即失效;

5、quick_exit

在C++11之前提供terminate和abort、exit等退出函数;
【terminate】:
- 当异常被抛出时,即会调用terminate,而其实际是调用abort;abort不会调用任何的析构函数,它将给系统抛出一个悉尼号,系统将会默认释放进程所有资源,该方式有时候会导致一些交互进程出现问题;
【exit】:
- 正常退出程序,会调用析构函数以及atexit做一些清理工作,调用析构将内存还给系统时将会很耗时;

C++11提供quick_exit:
- 该函数不执行析构,只是让程序终止,也会调用at_quick_exit做一些清理工作;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值