Had*_*ber 4 c++ embedded virtual vtable
我一直在为嵌入式环境开发一个简单的框架。我决定要使用虚拟调用,CRTP还是使用switch语句。有人告诉我vtables在嵌入式中的性能很差。
从这个问题开始,
vvable性能下降vs. switch语句,
我决定运行自己的测试。我用三种不同的方法来调用成员函数。
我从来没有尝试过使用基本的CRTP模式,但是etl :: function应该是该模式所使用的机制的变体。我使用MSVC并在ARM Cortex M4上获得类似性能的时间是
纯虚拟呼叫明显更快。我是想念某些东西还是虚拟通话,不如人们所说的那么糟糕。这是用于测试的代码。
class testetlFunc
{
public:
uint32_t a;
testetlFunc() { a = 0; };
void foo();
};
class testetlFunc2
{
public:
uint32_t a;
testetlFunc2() { a = 0; };
virtual void foo() = 0;
};
void testetlFunc::foo()
{
a++;
}
class testetlFuncDerived : public testetlFunc2
{
public:
testetlFuncDerived();
void foo() override;
};
testetlFuncDerived::testetlFuncDerived()
{
}
void testetlFuncDerived::foo()
{
a++;
}
etl::ifunction<void>* timer1_callback1;
etl::ifunction<void>* timer1_callback2;
etl::ifunction<void>* timer1_callback3;
etl::ifunction<void>* timer1_callback4;
etl::ifunction<void>* etlcallbacks[4];
testetlFunc ttt;
testetlFunc ttt2;
testetlFunc ttt3;
testetlFunc ttt4;
testetlFuncDerived tttd1;
testetlFuncDerived tttd2;
testetlFuncDerived tttd3;
testetlFuncDerived tttd4;
testetlFunc2* tttarr[4];
static void MasterCallingFunction(uint16_t ID) {
switch (ID)
{
case 1:
ttt.foo();
break;
case 2:
ttt2.foo();
break;
case 3:
ttt3.foo();
break;
case 4:
ttt4.foo();
break;
default:
break;
}
};
int main()
{
tttarr[0] = (testetlFunc2*)&tttd1;
tttarr[1] = (testetlFunc2*)&tttd2;
tttarr[2] = (testetlFunc2*)&tttd3;
tttarr[3] = (testetlFunc2*)&tttd4;
etl::function_imv<testetlFunc, ttt, &testetlFunc::foo> k;
timer1_callback1 = &k;
etl::function_imv<testetlFunc, ttt2, &testetlFunc::foo> k2;
timer1_callback2 = &k2;
etl::function_imv<testetlFunc, ttt3, &testetlFunc::foo> k3;
timer1_callback3 = &k3;
etl::function_imv<testetlFunc, ttt4, &testetlFunc::foo> k4;
timer1_callback4 = &k4;
etlcallbacks[0] = timer1_callback1;
etlcallbacks[1] = timer1_callback2;
etlcallbacks[2] = timer1_callback3;
etlcallbacks[3] = timer1_callback4;
//results for etl::function --------------
int rng;
srand(time(0));
StartTimer(1)
for (uint32_t i = 0; i < 2000000; i++)
{
rng = rand() % 4 + 0;
for (uint16_t j= 0; j < 4; j++)
{
(*etlcallbacks[rng])();
}
}
StopTimer(1)
//results for switch --------------
StartTimer(2)
for (uint32_t i = 0; i < 2000000; i++)
{
rng = rand() % 4 + 0;
for (uint16_t j = 0; j < 4; j++)
{
MasterCallingFunction(rng);
}
}
StopTimer(2)
//results for virtual vtable --------------
StartTimer(3)
for (uint32_t i = 0; i < 2000000; i++)
{
rng = rand() % 4 + 0;
for (uint16_t j = 0; j < 4; j++)
{
tttarr[rng]->foo();
//ttt.foo();
}
}
StopTimer(3)
PrintAllTimerDuration
}
Run Code Online (Sandbox Code Playgroud)
如果您真正需要的是虚拟调度,则C ++的虚拟调用可能是您可以获得的性能最高的实现,应该使用它们。数十位编译器工程师一直致力于优化它们,以使其能够获得最佳性能。
根据我的经验,当人们不需要虚拟方法时,他们会说避免使用虚拟方法。避免在可以静态分派的方法上以及代码中的热点上使用virtual关键字。
每次调用对象的虚拟方法时,都会发生这样的情况:访问对象的v表(可能会破坏内存位置并刷新一两个缓存),然后将取消引用指针以获取实际函数地址,并且那么实际的函数调用就会发生。这仅慢了几分之一秒,但是如果您在循环中将分数慢得足够慢,它会突然有所作为。
当您调用静态方法时,没有任何较早的操作发生。实际的函数调用只是发生。如果调用的函数和被调用的函数在内存中彼此接近,则所有缓存都可以保持原样。
因此,避免在紧密循环中的高性能或低CPU功耗情况下进行虚拟调度(例如,可以打开成员变量并调用包含整个循环的方法)。
但是有句俗话:“过早的优化是万恶之源”。事先评估性能。与几年前相比,“嵌入式” CPU变得更快,更强大。与仅适用于新的或特殊的CPU相比,用于流行CPU的编译器的优化效果更好。可能仅仅是编译器具有可减轻任何问题的优化器,或者您的CPU与普通的台式机CPU足够相似,从而可以从更流行的CPU中受益。
或者,您可能比告诉您避免虚拟呼叫的人员拥有更多的RAM等。
因此,进行概要分析,如果概要分析器说还可以,那就很好。还要确保您的测试具有代表性。您的测试代码的编写方式可能是:网络请求先于switch语句被抢占,并使它看起来比实际速度慢,或者虚拟方法调用受益于非虚拟调用加载的缓存。 。
| 归档时间: |
|
| 查看次数: |
129 次 |
| 最近记录: |