Polymorphic Inline Caches explained



原文地址:http://jayconrod.com/posts/44/polymorphic-inline-caches-explained

I'm moving to a new team at work where I'll be optimizing the V8 Javascript JIT compiler for Android. To prepare for this new role, I've been reading a lot of research papers on JIT compilation since I've mainly worked with static, compiled languages in the past. There are some pretty cool things you can accomplish when you're generating native code on the fly. Most of them stem from the fact that you don't have to generate code just once: you can rewrite and recompile code at any time using runtime information.

A particularly clever example of this is the Polymorphic Inline Cache. As presented by Urs Hölzle in Optimizing Dynamically-Typed Object-Oriented Languages With Polymorphic Inline Caches [PDF], PICs are a way to optimize polymorphic function calls in dynamic languages.

Let's say we have the following polymorphic function call. How would we compile it normally in a JITed language?

function feed(animal, food) {
  ...
  animal.munch(food);
  ...
}

We don't have any type information about animal, so we can't call themunch method directly. We have to look it up. If we were compiling code in a statically typed language, we would probably do a vtable lookup, which is fairly fast. However, in a dynamically typed language, there may be many classes with a munch method with no inheritance relationship, so it's impossible to know a vtable offset ahead of time. So we are forced to do a hash table lookup and probably a string comparison.

Most call sites actually only deal with one class though. This is called a monomorphic call site. At these sites, we should only have to look up the method only once. Since we can dynamically rewrite JITed code, we can just have the lookup function rewrite the call as a direct call to the target method.

This would work perfectly if we could guarantee objects of another class would not be used at the same call site later. No such guarantee is generally possible though, so we have to add some code which makes sure the target object is of the correct class. If it's not, we fall back to calling the lookup function. This type checking code can be inserted at the beginning of every method which may be called polymorphically. We can save memory by adding the checking code per method rather than per call site, since, in general, there ought to be fewer methods than call sites.

This works well for monomorphic call sites, but what about call sites that actually call methods in multiple classes? These are true polymorphic call sites. Polymorphic inline caches are meant to optimize this kind of call.

When we detect a "cache miss" from a monomorphic call site, i.e., the type check fails in a method, we create a stub function, which compares the target object's class against a small number of previously used (cached) classes. If it finds a match, it calls the corresponding method directly. If not, it calls the lookup function, which will add a new entry to the stub. The original call site is rewritten to call the stub directly.

Note that when we call a method from a stub, we already know we have the right class, so we can skip the type checking code at the beginning of the target method.

What about call sites which call methods on lots of different classes? Maybe the feed() function is used as part of a zoo simulator, and it's called for hundreds of different species. These are called megamorphic call sites, and PICs don't really make sense for these. Looking up a method in a megamorphic PIC would be even slower than just looking it up in a hash table, so it's best to limit the size of each PIC to a small number of entries.

Megamorphic call sites are pretty rare though, and PICs are useful most of the time. We can even do some additional optimizations once a PIC has been called enough times to be stable. We can rewrite the calling routine to inline the entire PIC. This won't use any extra memory since we have at most one PIC per call site anyway. Also, if we find one class is being used more than others, and the target method is short enough, we can even inline the method itself.

This kind of inlining is not possible in an ahead-of-time compiler without some elaborate profile guided optimization. You can't inline a polymorphic function call, since you don't know, statically, which method should actually be called. Inlining is a really important optimization, too, since it not only eliminates function call overhead but also enables other optimizations, such as improved instruction scheduling. So this is actually a case where JIT compiled code has the potential to outperform ahead-of-time compiled code.

在 C++ 中,source type is not polymorphic 是一个编译错误,表示源类型不是多态类型。多态类型是指至少包含一个虚函数的类型,通过基类指针或引用可以访问其派生类对象的成员函数。如果源类型不是多态类型,那么无法使用 dynamic_cast 将其指针或引用转换为派生类指针或引用。 例如,下面的代码会出现 source type is not polymorphic 错误: ``` class Base { public: void func() { cout << "This is Base class" << endl; } }; class Sub : public Base { public: void sub_func() { cout << "This is Sub class special function" << endl; } }; int main() { Base base_obj; Sub *sub_ptr = dynamic_cast<Sub*>(&base_obj); // 错误:source type is not polymorphic return 0; } ``` 因为 Base 类中没有虚函数,所以 Base 类不是多态类型,无法进行 dynamic_cast 转换。解决该错误的方法是在 Base 类中添加虚函数,将其变成多态类型。例如: ``` class Base { public: virtual void func() { cout << "This is Base class" << endl; } }; class Sub : public Base { public: void sub_func() { cout << "This is Sub class special function" << endl; } }; int main() { Sub sub_obj; Base *base_ptr = &sub_obj; Sub *sub_ptr = dynamic_cast<Sub*>(base_ptr); // 正确:进行子类向下转型 if (sub_ptr != nullptr) { sub_ptr->sub_func(); // 输出:This is Sub class special function } return 0; } ``` 在 Base 类中添加了虚函数 func,将其变成多态类型,可以正确进行 dynamic_cast 转换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值