我在C中写了一些数值模拟代码.在这个模拟中,有些东西是“本地的”,在二维网格上的每个点都有浮点值,而其他东西是“全局的”,只有一个全局浮点值.
除了这种差异,这两种类型的对象表现相似,因此我希望能够拥有一个包含两种类型对象的数组.但是,因为这是一个数值模拟,我需要这样做:(a)尽可能避免虚函数调用开销,(b)允许编译器尽可能多地使用优化 – 特别是允许编译器在可能的情况下进行SIMD自动编辑.
目前我发现自己正在编写这样的代码(我现在意识到,实际上并不按预期工作):
class Base {};
class Local: public Base {
public:
float data[size];
// plus constructors etc.
};
class Global: public Base {
public:
float data;
// ...
};
void doStuff(Local a, Local b) {
for (int i; i<size; ++i) {
a.data[i] += b.data[i];
}
}
void doStuff(Local a, Global b) {
for (int i; i<size; ++i) {
a.data[i] += b.data;
}
}
void doStuff(Global a, Local b) {
for (int i; i<size; ++i) {
a.data += b.data[i];
}
}
void doStuff(Global a, Global b) {
a.data += b.data*size;
}
我的代码比这复杂一点 – 数组是二维的,有几个doStuff类型的函数有三个而不是两个参数,所以我必须为每一个编写八个特化.
这不能按预期工作的原因是doStuff的参数类型在编译时实际上是不知道的.我想要做的是拥有一个Base *数组并在其两个成员上调用doStuff.然后我想要为其参数的特定类型调用doStuff的正确特化. (如果在doStuff中涉及虚拟方法调用并不重要 – 我只想在内循环中避免它们.)
这样做的意义而不是(例如)重载operator []是编译器可以(希望)为doStuff(本地,本地)和doStuff(本地,全局)执行SIMD自动矢量化,我可能会丢失完全循环在doStuff(Global,Global)中.也许在这些函数中也可能发生其他编译器优化.
然而,必须编写这样的重复代码是令人讨厌的.因此,我想知道是否有办法使用模板来实现这一点,这样我就可以编写一个函数doStuff(Base,Base),并生成与上面相同的代码. (我希望gcc足够聪明,可以在doStuff(Global,Global)的情况下优化循环.)
我强调以下解决方案不是我正在寻找的,因为它涉及通过循环的每次迭代的虚函数调用,这增加了开销并且可能阻止了许多编译器优化.
class Base {
virtual float &operator[](int) = 0;
};
class Local: public Base {
float data[size];
public:
float &operator[](int i) {
return data[i];
}
// …
};
class Global: public Base {
float data;
public:
float &operator[](int i) {
return data;
}
// ...
};
void doStuff(Base a, Base b) {
for (int i; i<size; ++i) {
a[i] += b[i];
}
}
我想实现与上面类似的效果,但是没有通过内部循环的每次迭代的虚函数调用的开销. (除非我完全错了,编译器实际上可以优化所有虚函数调用并生成如上所述的代码.在这种情况下,通过告诉我这可以节省我很多时间!)
我确实看过CRTP,但是由于doStuff的多个重载参数,如何使它适应这种情况并不明显,至少对我不适应.
最佳答案 你几乎得到了答案.像这样的模板函数应该可以工作(虽然我不知道大小来自哪里):
template<typename A, typename B>
void doStuff(A & a, B & b) {
for (int i; i<size; ++i) {
a[i] += b[i];
}
}
这里有一个重载的运算符[],但它不是虚拟的.
如果您在通话时不知道您有哪些类型,但是您有固定数量的派生类型,那么创建静态调度是一种选择
void doStuff( Base & a, Base & b ) {
Local * a_local = dynamic_cast<Local*>(&a);
Global * a_global = dynamic_cast<Global*>(&a);
//same for b
if( a_local && b_local ) {
doStuffImpl(*a, *b); {
} else if( a_local && b_global ) {
doStuffImpl(*a, *b):
} ...
}
假设doStuffImpl是模板函数,您会注意到if块中的代码对于每个条件都是相同的.我建议将其包装在宏中以减少代码开销.您可能还希望自己跟踪类型,不使用dynamic_cast.在Base类中有一个明确列出类型的枚举.这是一种安全机制,基本上可以防止未知的派生类出现在doStuff中.
不幸的是,这种方法是必需的.这是从动态类型转换为静态类型的唯一方法.如果你想使用模板,你需要静态模板.