During the last two weeks I’ve implemented an improvement requested by another team at rubicon: to make re-motion mixins hold on to fewer objects.
You see, when a mixed type is instantiated for the first time, re-motion’s mixin engine generates code to represent the combination of the target type with its mixins. Before that code generation takes place, an object model of the mixed type is created, called the target class definition. It is a very verbose, interlinked collection of all the metadata that’s required to generate mixed types. The reason for its existence is that it makes code generation quite self-contained, everything more modular, etc. So far, so good.
Not so good was an idea I had immediately after conceiving the target class definition stuff. See, you have all that information that is used to generate code for mixed types. And then, you have a feature request for something called mixin reflection. This means asking questions such as, “Given a mixed type’s member, find out whether it was introduced by a mixin or comes from the target class.” Or, “Given a mixed type, tell me what mixins are applied to it.” This is exactly the kind of information held by the target class definition and its fellow objects! So what could be better than simply caching the definition model, keeping it around in the background just in case somebody asks questions such as the ones above?
Great idea, simple implementation, I thought: here came the TargetClasDefinitionCache. Feed it a ClassContext (which represents a combination of target type and mixin types), and it will give you all the information you need.
Or will it? Actually, it will give you all the information the code generator needs. And chances are very good that you don’t need all that information. Unless you are a code generator, of course. Chances are, in fact, extremely high that TargetClassDefinitionCache will keep an object graph with lots of definition object instances, dictionaries, lists, and so on alive – that no one will ever use!
Okay, that was a little too dramatic. After all, it was the simplest way (that could possibly work) of implementing the features of mixin reflection. And, it was not likely to bite you – I mean, how many classes would you mix in your application? 10? 50? Okay, you’ll have hundreds of dictionaries; so what?
It turned out that actually mixins were used in very big applications with many, many mixed classes. And so, the target class definition cache kept alive a lot of objects. That didn’t necessarily generate a performance problem, but it effectively made memory profiling near to impossible. Many existing profiling tools just give up if your application holds static references to hundreds of thousands of objects right after startup.
Next idea: don’t let the cache grow indefinitely, right? After all, it’s a cache, just devise some intelligent strategy of emptying it!
Well… You know. I made a mistake. It wasn’t really meant to be a cache. It was more like a data store. I just assumed (and at that time, it was true) that nobody would be hurt by my keeping all the target class definitions around. Therefore, the definitions were used as keys all over the place (reference equality!), and every generated type had a back-reference to its TargetClassDefinition object. (You could access it from a mixed instance via IMixinTarget.Configuration, btw.) So even if I pruned the TargetClassDefinitionCache, every loaded mixed type would still hold on to its object model. Tightly.
So, to make a long story short, both TargetClassDefinitionCache and the back-references are now history, all hail to TargetClassDefinitionFactory. With it, the target class definition is now generated solely for code generation, then discarded (= made collectable by the GC). In case you’re using the TypeMixer – pregenerating code and loading the generated code into memory using ConcreteTypeBuilder.LoadAssemblyIntoCache – no target class definitions should be generated at all.
And if you fear that now mixin reflection might be much slower than before – fear not: I was able to rewrite much of it using ClassContexts, which are much more light-weight (and have value semantics when compared via Equals). For all other mixin reflection tasks, the generated type and attributes attached to it are now used. Which – performance-wise – means a single call to GetCustomAttributes in most cases. (Of course, the first time a reflection call is made on a mixed type, the type is generated unless it’s already in the cache. But it usually is.)
So, this is the final picture:
Makes a lot more sense, I think.