【发布时间】:2010-12-07 01:20:40
【问题描述】:
请考虑以下代码:
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
struct C : public B { int go() { return 2; } };
int main()
{
B b;
B &b_ref = b;
return b_ref.go();
}
在 GCC 4.4.1(使用 -O2)下,对 B::go() 的调用被内联(即,不会发生虚拟调度)。这意味着编译器承认a_ref 确实指向B 类型变量。 B 引用可用于指向 C,但编译器足够聪明,可以预见到情况并非如此,因此它完全优化了函数调用,内联函数。
太棒了!这是一个令人难以置信的优化。
那么,为什么 GCC 在以下情况下不做同样的事情呢?
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
struct C : public B { int go() { return 2; } };
int main()
{
B b;
A &b_ref = b;
return b_ref.go(); // B::go() is not inlined here, and a virtual dispatch is issued
}
有什么想法吗?其他编译器呢?这种优化常见吗? (我对这种编译器洞察力非常陌生,所以我很好奇)
如果第二种情况有效,我可以创建一些非常棒的模板,例如:
template <typename T>
class static_ptr_container
{
public:
typedef T st_ptr_value_type;
operator T *() { return &value; }
operator const T *() const { return &value; }
T *operator ->() { return &value; }
const T *operator ->() const { return &value; }
T *get() { return &value; }
const T *get() const { return &value; }
private:
T value;
};
template <typename T>
class static_ptr
{
public:
typedef static_ptr_container<T> container_type;
typedef T st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<static_ptr_container<T>>
{
public:
typedef static_ptr_container<T> container_type;
typedef typename container_type::st_ptr_value_type st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<const T>
{
public:
typedef const static_ptr_container<T> container_type;
typedef const T st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<const static_ptr_container<T>>
{
public:
typedef const static_ptr_container<T> container_type;
typedef typename container_type::st_ptr_value_type st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
在许多情况下,这些模板可用于避免虚拟调度:
// without static_ptr<>
void func(B &ref);
int main()
{
B b;
func(b); // since func() can't be inlined, there is no telling I'm not
// gonna pass it a reference to a derivation of `B`
return 0;
}
// with static_ptr<>
void func(static_ptr<B> ref);
int main()
{
static_ptr_container<B> b;
func(b); // here, func() could inline operator->() from static_ptr<> and
// static_ptr_container<> and be dead-sure it's dealing with an object
// `B`; in cases func() is really *only* meant for `B`, static_ptr<>
// serves both as a compile-time restriction for that type (great!)
// AND as a big runtime optimization if func() uses `B`'s
// virtual methods a lot -- and even gets to explore inlining
// when possible
return 0;
}
实现它是否可行? (不要继续说这是一个微优化,因为它很可能是一个巨大的优化..)
--编辑
我刚刚注意到static_ptr<> 的问题与我暴露的问题无关。指针类型被保留,但它仍然没有内联。我猜 GCC 并没有深入到找出 static_ptr_container::value 不是引用也不是指针所需的深度。对于那个很抱歉。但是这个问题仍然没有答案。
--编辑
我已经制定了一个实际有效的static_ptr<> 版本。我也改了一下名字:
template <typename T>
struct static_type_container
{
// uncomment this constructor if you can't use C++0x
template <typename ... CtorArgs>
static_type_container(CtorArgs ... args)
: value(std::forward<CtorArgs>(args)...) {}
T value; // yes, it's that stupid.
};
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
inline int func(static_type_container<Derived> *ptr)
{
return ptr->value.go(); // B::go() gets inlined here, since
// static_type_container<Derived>::value
// is known to be always of type Derived
}
int main()
{
static_type_container<Derived> d;
return func(&d); // func() also gets inlined, resulting in main()
// that simply returns 1, as if it was a constant
}
唯一的弱点是用户必须访问ptr->value 才能获取实际对象。重载operator ->() 在 GCC 中不起作用。任何返回对实际对象的引用的方法(如果它是内联的)都会破坏优化。好可惜啊。。
【问题讨论】:
-
您还在为虚函数调度的成本而烦恼吗?成本实际上是无法衡量的。在大多数复杂系统中,额外查找的成本与几乎任何其他使处理器停止的操作相比相形见绌(这种情况经常发生)。在大多数情况下,代码清晰度比速度更重要(额外的速度增益不值得人类阅读代码的额外复杂性)。
-
我最近读了很多关于它的内容,我认为这是一个编译器工作......你应该阅读“C++ 静态面向对象编程”。他们像你一样大量使用元编程。
-
如果你仔细观察,你会发现绝对没有额外的查找。我之所以采用这种推理方式,是因为我想编写一个通用接口,以使用三个不同的基础库来实现;由于可以实例化两个实现,因此用户可以混合变量,例如使用
B和A的实例,从而弄得一团糟。此外,整个系统将在一个接口后面,对系统的每一点都进行虚拟函数调度。那不是我想要的。这是一个 3D 图形库。 -
@MartinYork:是的,我知道。我不会在商业项目中做这些事情,但这是我的一个实验项目,我正是用它来尝试这种事情。在使用 C++ 几年之后,如果不做一些不会让你被同事责备或被解雇的事情,就很难深入了解这门语言(:
-
@n2liquid:我的出发点是homepages.fh-regensburg.de/~mpool/mpool08/submissions/… - SCOOP2 范例 - 然后我关注了我感兴趣的参考资料(并且可以在互联网上免费获得......)。它的主要目的是谈论通用性但具有高性能,因此静态内容与元编程有关。
标签: c++ optimization metaprogramming compiler-optimization gcc4