避免检查可能的如果

Avoiding Checking likely if

本文关键字:如果 检查      更新时间:2023-10-16

考虑到以下内容:

class ReadWrite {
public:
    int Read(size_t address);
    void Write(size_t address, int val);
private:
    std::map<size_t, int> db;
}

在读取函数中,当访问之前没有写入的地址时,我想抛出异常指定此类错误或允许并返回0,换句话说,我想使用std::map<size_t, int>::operator[]()std::map<size_t, int>::at(),这取决于用户可以设置的一些bool值。所以我添加了以下内容:

class ReadWrite {
public:
    int Read(size_t add) { if (allow) return db[add]; return db.at(add);}
    void Write(size_t add, int val) { db[add] = val; }
    void Allow() { allow = true; }
private:
    bool allow = false;
    std::map<size_t, int> db;
}

问题是:通常,程序将在程序开始时调用allow或none,然后进行多次访问。所以,性能方面,这段代码很糟糕,因为它每次执行检查if (allow),通常它要么总是为真,要么总是为假。那么如何解决这个问题呢?

编辑:

虽然这个类的描述用例(一个或没有Allow())很可能是不确定的,所以我必须允许用户动态调用Allow()

另一个编辑:

使用函数指针的解决方案:使用不能由编译器内联的函数指针所产生的性能开销是什么?如果我们使用std::function代替会解决问题吗?

通常情况下,程序将在对象处调用allow或none程序的开始和之后的许多访问。所以,性能方面,这段代码很糟糕,因为它每次都执行检查if (allow)在通常为true或always的地方假的。那么如何解决这个问题呢?

我不会,中央处理器会。
分支预测将计算出答案很可能在很长一段时间内是相同的,因此它将能够在硬件级别上非常优化分支。它仍然会产生一些开销,但是可以忽略不计。

如果你真的需要优化你的程序,我认为你最好使用std::unordered_map而不是std::map,或者转向一些更快的映射实现,比如google::dense_hash_map。与地图查找相比,分支是微不足道的。

如果想要减少时间成本,就必须增加内存成本。接受这一点,您可以使用函数指针来实现这一点。下面是我的答案:

class ReadWrite {
public:
   void Write(size_t add, int val) { db[add] = val; }
   // when allowed, make the function pointer point to read2
   void Allow() { Read = &ReadWrite::read2;}
   //function pointer that points to read1 by default
   int (ReadWrite::*Read)(size_t) = &ReadWrite::read1;
private: 
   int read1(size_t add){return db.at(add);}
   int read2(size_t add) {return db[add];}
   std::map<size_t, int> db;
};

函数指针可以作为其他成员函数调用。例如:

ReadWrite rwObject;
//some code here
//...
rwObject.Read(5); //use of function pointer
//

请注意,非静态数据成员初始化在c++11中可用,因此int (ReadWrite::*Read)(size_t) = &ReadWrite::read1;可能无法在旧版本中编译。在这种情况下,必须显式声明一个构造函数,在该构造函数中可以完成函数指针的初始化。

可以使用指针来实现函数

class ReadWrite {
public:
    void Write(size_t add, int val) { db[add] = val; }
    int Read(size_t add) { (this->*Rfunc)(add); }
    void Allow() { Rfunc = &ReadWrite::Read2; }
private:
    std::map<size_t, int> db;
    int Read1(size_t add) { return db.at(add); }
    int Read2(size_t add) { return db[add]; }
    int (ReadWrite::*Rfunc)(size_t) = &ReadWrite::Read1;
}

如果你想要运行时动态行为,你必须在运行时(在你想要你的逻辑动态行为的时候)为它付费。

你希望在调用Read时根据运行时条件有不同的行为,你必须检查那个条件。无论您的overhad是函数指针调用还是分支,您都会发现在客户端代码调用Read时,根据allow,您的程序中会跳转或调用到不同的位置。

注意:分析和修复真正的瓶颈——而不是可疑的瓶颈。(如果你能通过证实你的怀疑或者找出为什么你对业绩的假设是错误的,你会学到更多。)