4 ms·
I think it's part bug and part having a GC with only two generations (old and young). When you have to choose between putting these tweener objects somewhere, y
by rubiquity 13y ago
I think it's part bug and part having a GC with only two generations (old and young). When you have to choose between putting these tweener objects somewhere, you have to be more conservative and move them to the old generation. Once a third generation is added (Ruby 2.2?) this will be much smoother.
> For an app like Discourse 3-10% of request time is occupied looking up methods, due to cache inefficiency.
Hmmm, I thought Ruby 2.1 already had a per-class method cache, or maybe it was just a per-class method cache invalidation, but I don't know how you coud have one without the other. I'll have to reinvestigate this.
> That's amazing, and demonstrates that there's probably still quite a lot of low-hanging performance fruit that Ruby can look to exploit.
I'm not sure I share as much of a positive outlook. Short of adding JIT compilation, I think the gains from here on out will start to get smaller and smaller. The performance gains of RGenGC were very impressive, though.
- vidarh 13y agoI'm working on a "as static as possible" Ruby compiler as a hobby project, and it's incredibly frustrating at times to see the generated code grow to ridiculous size as I'm getting closer to actually complying with real Ruby semantics... But I do still think there are substantial gains possible. For starters, for most method calls there's no reason to do the expensive method lookups that MRI still uses - cache or no cache - you can use C++ style vtables, as long as you propagate updates to them downwards when a method is re-defined. You do need to be able to fall back to handle dynamically created methods with names not present when you generate the vtables, and optionally reduce waste (as the vtables needs to be the same size for all classes, with unimplemented methods replaced with pointers to method_missing thunks), but in terms of performance you can do fairly well and compared to this GC blowup, the memory waste would be small. But there's also not much alternative but going for proper JIT'ing of at least some things.
- pjmlp 13y agoDynamic languages tend to gain more from JIT as AOT due to such issues. On the other hand, have a look at Dylan, as it might inspire you: http://opendylan.org/ http://opendylan.org/
- vidarh 13y agoFor the method lookup, other than for methods that are dynamically generated with names not known at compile time, the only additional gain you'll get from JIT is by going to full on inline caches, but vtables gets you most of the speedup without the hassle of inline caches and tracing, and doesn't prevent using tracing and inline caching down the line.
- pjmlp 13y agoWith JITs you get devirtualization as well, so no need for vtables. Something possible in AOT as well to certain extent, but it requires a mix of profile guided optimizations coupled with whole programm analysis. Which have issues with dll/so anyway, as those calls cannot be optimized away as in JITs.
- vidarh 13y ago> With JITs you get devirtualization as well, so no need for vtables. That's what I referred to with "inline caches". The problem is that for Ruby you need fully polymorphic inline caches, with guards all over the place, because unless you do tons of analysis upfront, you will have problems knowing whether or not the world has totally changed on you after any method call, and almost anything is a method call. (call into code you have not verified can't possibly call "eval", and you might find that adding two integers afterwards does in fact not add them, but returns a string and changes global variables, and what-not) The upshot, is that compared to vtables, you're not actually saving all that much. E.g., take "1 + 2 - 3". You could inline Fixnum#+ (and could reasonably do so with an AOT compiler too). But you need to add a type guard before the inlined fragment to verify that Fixnum#+ still is the Fixnum#+ you inlined, which at the minimum costs you a comparison and a branch or you need to record every call-site with inlined code and be prepared to overwrite it with fixups if the implementation changes. And if Fixnum#+ has been overridden, or the Fixnum#+ implementation has method calls, chances are you will need another guard before "-" too, because you might not even know for sure whether or not the object returned from "1 + 2" will be a Fixnum, so you might find that the inlined method suddenly is for the wrong class. I'm planning on benchmarking inline caching for my compiler against vtables, but absent evidence to the contrary I'm expecting that there will be a very substantial number of cases where the complexity isn't worth it, or where they might even turn out to be slower. > Something possible in AOT as well to certain extent, but it requires a mix of profile guided optimizations coupled with whole programm analysis. It does if you want to do everything upfront, but you can pull things into inline caches with a mostly-AOT compiler relatively easily with just a little bit of extra information, and a few guards thrown in to do some basic tracing.