假设您调用了一个计算值并返回该值的方法:
double calculate(const double& someArg);
您实现了另一个与第一个具有相同配置文件但工作方式不同的计算方法:
double calculate2(const double& someArg);
你希望能够根据 bool 值设置从一个切换到另一个,所以你最终得到这样的结果:
double calculate(const double& someArg)
{
if (useFirstVersion) // <-- this is a boolean
return calculate1(someArg); // actual first implementation
else
return calculate2(someArg); // second implementation
}
bool 值可能会在运行时发生变化,但这种情况很少见。
我注意到一个小但明显的性能下降,我认为这是由于分支预测错误或缓存不友好的代码造成的。
如何对其进行优化以获得最佳的运行时性能?
我对这个问题的思考和尝试:
我尝试使用指向函数的指针来确保避免分支错误预测:
我的想法是当 bool 值改变时,我更新指向函数的指针。这样就没有if/else了,我们直接使用指针:
指针是这样定义的:
double (ClassWeAreIn::*pCalculate)(const double& someArg) const;
... 新的计算方法变成这样:
double calculate(const double& someArg)
{
(this->*(pCalculate))(someArg);
}
我尝试将它与 __forceinline 结合使用,它确实有所不同(我不确定这是否应该是预期的,因为编译器应该已经完成了?)。如果没有 __forceline,它的性能最差,而使用 __forceinline,它似乎要好得多。
我考虑过使用两个重写来计算一个虚拟方法,但我读到虚拟方法不是优化代码的好方法,因为我们仍然必须找到在运行时调用的正确方法。不过我没有尝试。
然而,无论我做了什么样的修改,我似乎永远无法恢复原来的表演(也许这是不可能的?)。是否有设计模式以最佳方式处理此问题(并且可能越清洁/越容易维护越好)?
VS 的完整示例:
主要.cpp
#include "stdafx.h"
#include "SomeClass.h"
#include <time.h>
#include <stdlib.h>
#include <chrono>
#include <iostream>
int main()
{
srand(time(NULL));
auto start = std::chrono::steady_clock::now();
SomeClass someClass;
double result;
for (long long i = 0; i < 1000000000; ++i)
result = someClass.calculate(0.784542);
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << diff.count() << std::endl;
return 0;
}
一些类.cpp
#include "stdafx.h"
#include "SomeClass.h"
#include <math.h>
#include <stdlib.h>
double SomeClass::calculate(const double& someArg)
{
if (useFirstVersion)
return calculate1(someArg);
else
return calculate2(someArg);
}
double SomeClass::calculate1(const double& someArg)
{
return asinf((rand() % 10 + someArg)/10);
}
double SomeClass::calculate2(const double& someArg)
{
return acosf((rand() % 10 + someArg) / 10);
}
一些类.h
#pragma once
class SomeClass
{
public:
bool useFirstVersion = true;
double calculate(const double& someArg);
double calculate1(const double& someArg);
double calculate2(const double& someArg);
};
(我没有在示例中包含 ptr 函数,因为它似乎只会让事情变得更糟)。
使用上面的示例,当在主函数中直接调用 calculate1 时,我平均需要 14.61 秒来运行它,而当调用 calculate0 时(使用 __forceinline,这似乎使间隙更小)。
最佳答案
由于 useFirstVersion
很少改变,calculate
的执行路径很容易被大多数分支预测技术预测到。由于实现 if/else 逻辑所需的额外代码,性能会有所下降。它还取决于编译器是否内联 calculate
、calculate1
或 calculate2
。理想情况下,所有这些都应该内联,尽管与直接调用 calculate1
或 calculate2
相比不太可能发生,因为代码量更大。请注意,我没有尝试重现您的结果,但对于 3% 的性能下降并没有什么特别可疑的地方。如果您可以使 useFirstVersion
永远不会动态更改,那么您可以将它变成一个宏。否则,通过函数指针调用 calculate
的想法将消除大部分性能开销。顺便说一句,我不认为 MSVC 可以通过函数指针内联调用,但这些函数是内联的良好候选者。
关于c++ - 使用基于 bool 值的两种方法之一调用方法时如何确保避免分支预测错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53073063/