c++20中的分支预测

一、分支预测

得益于CPU的流水线作业,使得计算机运算工的速度大有提高。但是,在一些情况下,意外的情况会打破流水线作业,此时,流水线反而成了一种负累,重新整装新的流水线,会花费更多的CPU时间。如果大家对流水线指令操作没了印象,最好回去翻一下《计算机原理》。这也是现代CPU设计的一个重大的进步,它仍然是基于二八原则来设计的或者局部运行原理。
而打破流水线中,一个重要的原因就是分支的预测,如果分支预测成功率很高,那么被打破流水线的可能性就会降低很多,那么流水线作业的效率就会大幅提高。同样,指令的来源就是编码的实现,所以,如果在编码实现就给一个基本的预测(当然不可能百分百准确),那么底层的流水线作业会更安全,命中效率会进一步增强。
那么什么是分支预测(Branch Prediction)呢?就是对下一步的指令进行分析后预判它更可能执行哪个指令。它分为软件静态分支预测和硬件动态分支预测。今天谈的c++20的分支预测指的是前者,即软件的静态分支预测。看一下维基百科的定义:
在电脑架构中,分支预测器(英语:Branchpredictor)是一种数字电路,在分支指令执行结束之前猜测哪一路分支将会被运行,以提高处理器的指令流水线的性能。使用分支预测器的目的,在于改善指令管线化的流程。现代使用指令管线化处理器的性能能够提高,分支预测器对于现今的指令流水线微处理器获得高性能是非常关键的技术。

二、c++20的分支预测

在c++20中提供了[[likely]]和[[unlikely]]两个关键字提供对分支更可能流向的方向,其实这个在Linux内核原来就有类似的宏,有过内核源码阅读经验的应该可以立刻想到。看一个简单的例子:

constexpr long long Sum(long long n) noexcept {
    if (n > 1) [[likely]]
    {
        return n + Sum(n - 1);
    }
    else [[unlikely]]
    {
        return 1;
    }
}

其实这就是告诉编译器,哪个更概率大的要执行。早在Gcc2.6就提供了__builtin_expect()这个函数,用来对分支转移提供判断:

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

再看一个文档中提供的例程:

int f(int i)
{
    switch(i) {
    case 1: [[fallthrough]];
    [[likely]] case 2: return 1;
    }
    return 2;
}

c++20通过属性关键字来支持分支预测,官方的说明文档上是这样说的:
“indicates that the compiler should optimize for the case where a path of execution through a statement is more or less likely than any other path of execution”意思是“指示编译器应该针对通过某语句的执行路径比任何其他执行路径更可能或更不可能的情况进行优化。”
越来越像一些其它语言,这个其它,应该都知道是什么吧。

三、总结

虽然说软件分支测试可以做到七成的把握,但是对于现代深流水线设计的CPU,代价仍然是非常高的。同样,也不要迷信静态分支预测,现代的编译器做的已经非常不错了,个人在编码层次上做的一些优化,其实也无法做到决定性的作用,只能说在局部的一些特定的场合,优化还是有意义的。
在降低分支跳转的编码中,有很多类似于极客的代码,执行效率高,但阅读的难度和维护的难度急剧增长,这对于现代软件工程来说,有得不偿失的感觉,只有在OS底层或者说特定的场合下,一般是不推荐这样编写代码的。毕竟,现代软件工程是一个集体协作创造的过程。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/122176776