我试图将对象存储在多种容器类型之一中,容器的类型在运行时是已知的。 容器的界面是相同,但容器不多态。
我想要做的是避免在运行时额外跳转vtable力 - 通过具有应该是具体容器类型的变量。
考虑一下我有容器:
class Vector1<T>
{
T get();
}
class Vector2<T>
{
T get();
}
我希望有一个容器,它可以是Vector1或Vector2,具体取决于某些运行时参数。
所以我的班级应该是:
Class VectorWrapper
{
Vector1<SomeType> v1;
Vector2<Sometype> v2;
inline Sometype get()
{
**how do I choose between v1 and v2, without the extra jump?**
}
}
答案 0 :(得分:3)
为了避免在运行时决定,您必须在编译时做出决定。这意味着使用模板。如果你的时间有限,以至于关键路径上肯定没有必要的跳跃,那么你必须继续在你的程序中越来越高地推动tempaltedness,直到你达到额外的跳跃可以制作。是的,理论上这可能意味着在模板中有> = 90%的代码,但如果真的需要避免跳转,那就是价格。
换句话说,如果VectorWrapper
不能使用if
决定,那么它也必须是模板。所以必须是使用它的代码等,直到你最终达到if
不那么昂贵的程度。
如果你发现这个模板化部分太大,但至少可以以某种方式被隔离,你甚至可以做一些事情,比如构建几个共享库(每个库中有不同的模板参数),并在运行时加载一个包含正确组合模板参数。
答案 1 :(得分:1)
你的问题很模糊。所以我能提供的只是模糊的解决方案。
值得考虑的第一项技术是手动vtable。与自动vtable相比,这消除了内存间接。基本上,您将函数指针存储到方法的正确实现中,并直接跟随该函数指针。
第二种方法是将分支从循环中提升出来。重要的是,您的代码必须重复运行。如果决定在重复之前采取哪个分支,则可以将分支移出那里。
想象一下你有
void foo() {
bool which_branch = decide();
VectorWrapper wrap( decide );
wrap.populate();
for (auto x : some_loop_domain) {
x.set( wrap.get() );
}
}
请注意,决定是在循环之外做出的,但价格可以在循环内支付。
如果我们可以将分支移到循环之外,则some_loop_domain
每个元素支付一次而不是一次。
使这项工作的一种方法是为编译器提供更容易优化的常量。因此,我们避免在包装器中存储使用向量的状态,而是将其传入。我们传递的值被设置为&#34;显然是常数&#34;在使用它的上下文中,甚至是编译时常量。
要使用编译时常量,使用它的代码必须加上寺庙化。然而,这种寺庙化只需要在循环之上&#34;点,作为一个分支,通常便宜到不关心。
为了依赖优化器是合理的,你同样在那里填充一个明显的常量并将其作为值传递下去。根据我的经验,这可能不太可靠。
void foo() {
bool which_branch = decide();
auto body = [&](auto which_branch) {
VectorWrapper wrap( which_branch );
wrap.populate();
for (auto x : some_loop_domain) {
x.set( wrap.get() );
}
};
if (which_branch) {
body( std::true_type{} );
} else {
body( std::false_type{} );
}
}
现在在body
内,which_branch
是一个编译时常量。所有这一切都可能导致分支被吊起,但可能需要:
auto body = [&](auto which_branch) {
static_assert( decltype(which_branch){} || !decltype(which_branch){} );
VectorWrapper<decltype(which_branch)> wrap;
wrap.populate();
for (auto x : some_loop_domain) {
x.set( wrap.get() );
}
};
我们在VectorWrapper
上对template
bool
进行实例化,并在true
上下文中读取false
或constexpr
作为{{1} }}
VectorWrapper
中的代码保持相似,除了它读取编译时常量而不是决定采用哪个分支的方法。
这种技术可能非常具有侵入性或根本不具有侵入性。它可能需要看似分支的代码并将分支提升出来。
你可以用类似的方式做许多低级分支的笛卡尔积,你可以生成2 ^ 10个不同的低级例程,在进入循环之前在它们之间进行选择,然后运行1024个循环中的一个。 / p>
答案 2 :(得分:0)
使程序中使用容器类型参数化的容器的部分:
template<class Container>
void criticalPath()
{
// Create and use Container objects a lot
}
void enterCriticalPath(bool useVector1) {
if (useVector1) {
criticalPath<Vector1<SomeType>>();
} else {
criticalPath<Vector2<SomeType>>();
}
}