我正在评估将一款实时软件从C/汇编语言重写为C++/汇编语言(原因与问题无关的部分必须以汇编语言完成)。
每秒有3,000次中断,对于每个中断,需要按照顺序执行大约200个不同的操作。处理器运行速度为300 MHz,给我们100,000个周期来完成这项工作。通过一个函数指针数组,在C语言中已经解决了这个问题:
// Each function does a different thing, all take one parameter being a pointer
// to a struct, each struct also being different.
void (*todolist[200])(void *parameters);
// Array of pointers to structs containing each function's parameters.
void *paramlist[200];
void realtime(void)
{
int i;
for (i = 0; i < 200; i++)
(*todolist[i])(paramlist[i]);
}
速度很重要。上面的200次迭代每秒执行3,000次,因此实际上我们每秒执行600,000次迭代。上述循环每迭代5个周期,总共耗费3,000,000个周期,即1%的CPU负载。汇编优化可能将其降到4条指令,但我担心由于接近彼此的内存访问等原因会导致额外的延迟。简而言之,我认为这些五个周期是非常优化的。
现在转到C++重写。我们所做的这200件事情有某种联系。它们所有需要和使用的参数都属于一个子集,并在各自的结构体中使用。在C ++实现中,它们可以被整洁地视为继承自一个公共基类:
class Base
{
virtual void Execute();
int something_all_things_need;
}
class Derived1 : Base
{
void Execute() { /* Do something */ }
int own_parameter;
// Other own parameters
}
class Derived2 : Base { /* Etc. */ }
Base *todolist[200];
void realtime(void)
{
for (int i = 0; i < 200; i++)
todolist[i]->Execute(); // vtable look-up! 20+ cycles.
}
我的问题是vtable查找。我无法每秒执行600,000个查找;这将占用超过4%的CPU负载浪费。此外,todolist在运行时从不更改,它只在启动时设置一次,因此查找要调用的函数的工作确实是浪费的。在问自己“最优结果可能是什么”的问题时,我查看了C解决方案给出的汇编代码,并重新发现了一个函数指针数组...
在C ++中,如何正确地处理这个问题?制作一个好的基类、派生类等等,在最后为了性能原因再次挑选函数指针感觉相当无意义。
更新(包括循环开始位置的更正):
处理器是ADSP-214xx,编译器是VisualDSP++ 5.0。启用#pragma optimize_for_speed
时,C循环为9个周期。在我的想法中进行汇编优化可得4个周期,但我没有测试,因此不能保证。C++循环为14个周期。我知道编译器可以做得更好,但我不想把它归咎于编译器问题——在嵌入式环境中,没有多态仍然是首选,并且设计选择仍然让我感兴趣。供参考,以下是结果的汇编代码:
C:
i3=0xb27ba;
i5=0xb28e6;
r15=0xc8;
以下是实际的循环:
r4=dm(i5,m6);
i12=dm(i3,m6);
r2=i6;
i6=i7;
jump (m13,i12) (db);
dm(i7,m7)=r2;
dm(i7,m7)=0x1279de;
r15=r15-1;
if ne jump (pc, 0xfffffff2);
C++ :
(Note: This is already in Chinese. If you want me to translate it into another language, please let me know.)
i5=0xb279a;
r15=0xc8;
这是实际的循环:
i5=modify(i5,m6);
i4=dm(m7,i5);
r2=i4;
i4=dm(m6,i4);
r1=dm(0x3,i4);
r4=r2+r1;
i12=dm(0x5,i4);
r2=i6;
i6=i7;
jump (m13,i12) (db);
dm(i7,m7)=r2;
dm(i7,m7)=0x1279e2;
r15=r15-1;
if ne jump (pc, 0xffffffe7);
同时,我认为我已经找到了一种答案。最少的周期数是通过尽可能做最少的工作来实现的。我需要获取一个数据指针,获取一个函数指针,并将带有数据指针的参数调用该函数。获取指针时,索引寄存器会自动修改一个常量,而这个常量可以设置为1。因此,我们再次得到一个函数指针数组和一个数据指针数组。
当然,在汇编中能够做到的是极限,这已经得到了探索。有了这个想法,我现在明白了,即使引入一个基类很自然,但它并不是真正适合要求的东西。所以,我想答案是,如果你想要一个函数指针数组,你应该自己创建一个函数指针数组...