Saturday, February 25, 2017, 7:59:55 AM, David E Jones wrote: > These all sound like good changes, and clarify what is really going on.
(Basically, I'm decreasing code size and complexity before the more interesting changes are started.) > Removing object wrapping IMO is more important than removing the > *Template*Model* interfaces. (The two come hand in hand really. If we had no TemplateModel-s, we had no ObjectWrappers either.) > These interfaces don't have the same runtime and > GC overhead as object wrapping and they can be nice for type checking to make > sure the operation in the template can actually be done on the object. Still, > with those interfaces it would be nice to use more standard ones where they > exist (Java collection interfaces for example). > > On the object wrapping topic there might be some useful things to learn from > Groovy, though as FTL doesn't compile to a class not all of the same applies. > The biggest performance overhead in Groovy is all the type conversions that it > does to be a type optional scripting language. I've done a lot of work on > optimizing Groovy code since I made a decision years ago to write a big > framework mostly in Groovy. > > > > I don't know that this is directly applicable to the discussion, but here is > an article I wrote last year on some stuff I learned about Groovy > optimization, and it may be that we can avoid some of these issues: > > > > https://dzone.com/articles/how-to-make-groovy-as-fast-as-java > > > > Even Groovy has a sort of object wrapper, ie its meta class. It's one thing > that would be great to disable when you're not using the Groovy features that > rely on it... maybe someday I'll even look into this in Groovy itself or open > the discussion in that community (for now I'm up to my neck in work :) ). Luckily, I don't think this kind of performance is priority for a template engine like FreeMarker, or at least that it's a high enough priority to trump easy extendibility and such. That's because it's not a programming language. Its a kind of domain specific language basically. For something that generates output, hopefully you don't have lines that are executed for millions and millions of times, unless you also generate heck of a lot of output, which has to be stored or sent, which will then (hopefully...) dominate your performance anyway. Also, for us, a key difficulty is that in general we can't have assumptions about the data-model content. It's outside the template. I just don't know that in ${user.name}, what `user` will be. Sometimes though, the data-model is a bean, so if that's declared inside the template, then you have better chances. But... what if User has no fullName property, but some User subclass has, and you write `${user.fullName!'unknown'}`? Should now I also require the user to do an explicit type cast? Anyway, one thing is sure, templates must be able to operate in non-static mode. Adding an alternative static mode is way out of reach (given the resources we can burn). And without that... our performance options are rather limited. Well, we can pull some tricks, like with indy, even if you don't know what the type of `user` will be, you can during execution learn that it's in practice always an User, and then you can link User.getName() as if it was a "static" invocation (with a guard condition so if it's not an User after all, you can take the slow route). > A good step in the right direction might be to start with the use of standard > Java interfaces. For example when you do a #list it could be required that the > object you are iterating over implements either the List or Iterator > interfaces (perhaps with optimization to detect RandomAccess lists like > ArrayList to avoid creating an Iterator). > This doesn't get us all the way down > the road, but is a start. What I consider instead of ObjectWrapper+TemplateModel-s is the following. We don't wrap values into anything. Instead we will have an interface called MOPImplementation (where MOP stands for Meta Object Protocol), which declares methods for each *operations* that FTL is interested in, such as `int getSize(env, v)`, `boolean isEmpty(v, env)`, `Iterator<Object> getListItemIterator(v, env)`, `Object getParent(v, env)` (for XML DOM-s and such), etc. So if you, for example, need to #list a value, you get the MOPImplementation instance based on the class of the value (it's the class of the "raw" value, not a TemplateMode), and then call getListItemIterator on it, which either gives you the Iterator or throws an TemplateOperationNotSupportedException. A key idea behind this is that you do not have a MOPImplementation per value instance. Like if you have two List-s, they all will use the same shared MOPImplementation instance (something like o.a.f.core.mop.impl.ListMOPImplementation.INSTANCE) to access those lists. The MOPImplementation isn't bound to a value, it's only bound to the class of the value (i.e. you have different MOPImplementation instance for List-s and another for arrays, another for DOM node sets, etc.). In comparison, with ObjectWrapper-s you have to create at least as many TemplateSequenceModel instances as many distinct List instances you will touch, because TemplateModel-s are bound to a certain value instance. (It's "at least" as many, because if you access the same List instance for the second time, you often has re-create the TemplateModel, because caching the TemplateModel-s doesn't worth it usually. If you assign the list to an FTL variable, and then access the variable for multiple times, then no such re-wrapping will happen of course, because you store the TemplateModel in the variable, not the List directly.) So, as you see, it's practically as flexible as ObjectWrapper-s. We don't hard-wire what's listable, etc., because it all depends on the set of MOPInterface classes added to the Configuration. So you can still make classes that we have never seen to be listable, or change the standard behavior (without changing the source code), etc. But, I don't think it's a huge win in performance... it's hopefully some though: - You will generate less garbage to be GC-ed for sure, and that's good. - If you just pass through values the template, like in `obj.m(theValuePassedThrough)`, you don't have to do anything extra with it. (Currently, you have to wrap `theValuePassedThrough`, only to unwrap later... ouch!) - But... if you assign a value to an FTL variable, it won't keep the result of the "introspection" anymore (because you just store the original object, not a TemplateModel that already knows all about it), so each time you try to do something with it, the MOPImplementation has to be chosen based on the class of the value. There are tricks to mitigate this effect, such as side-storing MOPImplementation next to the variable, but it brings code complexity, and some constant overhead. - Using indy might becomes feasible, as now what to do only depends on the class of the value, not on the value itself. But my real agenda with this is to make the code base less daunting (because, we need attract contributors, not only users... this is marked like that), and to make the API more user friendly. TemplateModel-s, if you have them, are all over the place. For example, when a user wants to implement a directive in Java, he gets TemplateModel-s. The first thing they want to do is, quite invariable I pressume, unwrapping them to the classes they know. But because of the freedom ObjectWrapper-s and TemplateModel-s have, it's not even guaranteed that that's possible. Basically, I don't want FTL to have its own type system (as it has with TemplateModel-s). I just want it to define a set operations (listing something, getting the size of something, etc.) instead. That's much easier to grasp. And if you want your Java code to behave like FTL, you can get a MOPImplementation. > -David > > > >  > > > On Feb 24 2017, at 9:18 pm, Daniel Dekany <[email protected]> wrote: > >> In FM3, I propose removing some legacy complexity: > >> > >> \- Remove BeansWrapper, which is the super class of > DefaultObjectWrapper, and add its functionality directly to > DeafultObjectWrapper. That we have this class hierarchy has purely > historical reasons. (Even if FM2, using pure BeansWrapper is > deprecated, and causes lot of pain and confusion because it's still > possible.) > >> > >> \- Remove the following BeansWrapper/DefaultObjectWrapper > settings, because they just exist in FM2 so that you can chose > between FM 2.3.0 compatibility and a better functionality: > >> > >> - BeansWrapper.simpleMapWrapper (DefaultObjectWrapper ignores it) > - DefaultObjectWrapper.forceLegacyNonListCollections (will be in > effect false) > - DefaultObjectWrapper.iterableSupport (will be in effect true) >> > >> PS I know that there's an idea of removing > ObjectWrapper-s/TemplateModel-s altogether, and using a MOP instead, > but that's a fairly brutal change and who knows if it will be > affordable. So while I try not to invest much into > ObjectWrapper-s/TemplateModel-s until we figure that out, I do at > least want to remove stuff that we won't need for sure. And also, baby > steps, easy things first... keep FM3 working all the time. > >> > >> \-- > Thanks, > Daniel Dekany > -- Thanks, Daniel Dekany
