ibatis-user-java mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Clinton Begin <clinton.be...@gmail.com>
Subject Re: MapperMethod caching?
Date Fri, 01 Jan 2010 21:43:23 GMT
Thanks Eduardo.  I'm open to design suggestions, but will be judicious about
performance related refactorings or anything that introduces state into the
execution engine.

Here's a bit of design background:

When you read down the package structure of the org.apache.ibatis, generally
all state is ultimately stored within the Configuration class and the
various objects that it collects and manages.  Most of these are in the
mapping and cache packages.  The entire execution package (the 'execution
engine') is stateless (perhaps with one or two exceptions that I cannot
even  recall at the moment).  Every other package contains various
supporting utility classes.

So between the mapping package and the execution package, you have 99% of
what's important to iBATIS.  The mapping package is the state, largely dumb
state.  The execution package is the stateless and contains all of the
behavior and logic.

This separation is important to keeping the design as simple as possible.
iBATIS 1 used a "smart object" design, where all state and behavior was
co-located, that was inflexible and messy.  iBATIS 2 used a stateful
execution pipeline that was very fast, but extremely complicated.  Few could
understand that code.  iBATIS 3 focuses heavily on code that's both flexible
and easier to read, possibly at the cost of a bit of performance, until it
becomes a problem.

I've been really happy with this focus, as your suggestions are proof that
the code is succeeding (at least better than its predecessors) in that
you're able to read it, follow along, figure it out and make suggestions.

They are most welcome, even if we don't accept every one.

Cheers,
Clinton


On Fri, Jan 1, 2010 at 1:57 PM, Eduardo Macarron <eduardo.macarron@gmail.com
> wrote:

> Clinton, you are completely right. On current design, and doing some very
> basic measures, caching MapperMethods does not provide a significant
> performance improvement. In my laptop, building a MapperMethod for a simple
> method requires about 13 microseconds and getting if from a map is about 4
> microseconds. So as I said caching does not make a big difference.
>
> I wanted just to point that current design disables caching because
> MapperMethods depend on sessions. That dependency seems unnecesary and
> removing it with what seems a simple change may open the way to caching.
>
> I don't know if MapperMethod creation will be heavier in future versions.
> For example climbing up in interface hierarchy, filtering java.lang.Object
> method calls like toString(), performing more checks or maybe other features
> yet to come. In fact some "caching" is already been done because mappers
> methods are procesed to search statement annotations during startup.
>
> Anyway I am aware of the current status of the project and also that any
> change should have a good reason to be done.
>
> 2010/1/1 Clinton Begin <clinton.begin@gmail.com>
>
>> Do you have some profiler metrics or benchmarks to support the concern?
>> Even completely unoptimized, I'd be surprised if this presented any sort of
>> performance challenge on a modern VM.
>>
>> While the MapperMethod may look slow when you read the code, realize that
>> most of the for loops will execute either 0 or 1 times.  Or maybe a couple
>> of times for methods that have multiple parameters or the odd one that has
>> multiple annotations.  Even then, it's 1 - 4 times against fairly quick
>> code.  The biggest performance impact is probably some of the string
>> conversion and concatenation, which could probably be optimized.
>>
>> More important than the micro-optimizations though, is that MapperMethod
>> is only ever called on the outermost call from the consumer of a Mapper
>> interface.  So it's a very high level call... just enough to marshal it down
>> to a sqlSession.[select,insert,update,delete] call.
>>
>> Even at 40 TPS (a good benchmark for 1,000,000 concurrent requests in an 8
>> hour period) you're looking at literally 40 * 4 operations per second at
>> most.  I wouldn't be overly concerned with those calls.  Even the potential
>> creation and destruction of maybe a few hundred string objects under that
>> amount of load, is nothing more than light exercise for the garbage
>> collector.
>>
>> I think there may be room for improvement, but not enough to warrant
>> introducing state into a stateless design.
>>
>> If you have some interesting profiler metrics that show a potential
>> bottleneck, I'd be very interested in seeing it.
>>
>> Cheers,
>> Clinton
>>
>>
>> On Fri, Jan 1, 2010 at 9:17 AM, Eduardo Macarron <
>> eduardo.macarron@gmail.com> wrote:
>>
>>> Yes Clinton, is just for performance.
>>>
>>>
>>> 2010/1/1 Clinton Begin <clinton.begin@gmail.com>
>>>
>>> What problem are you trying to solve, or goal you're trying to achieve by
>>>> doing this?  Is it strictly a performance consideration?
>>>>
>>>> Clinton
>>>>
>>>>
>>>>
>>>> On Fri, Jan 1, 2010 at 7:31 AM, Eduardo Macarron <
>>>> eduardo.macarron@gmail.com> wrote:
>>>>
>>>>> Fist of all happy new year!
>>>>>
>>>>> Some work has been doing on Spring 3 and Ibatis 3 integration.
>>>>>
>>>>> Having a look at mappers internals we saw that a new MapperMethod is
>>>>> created (with some introspection work) on each call to a mapper method.
I
>>>>> wonder if it would be better to build them all during startup or maybe
cache
>>>>> them somehow.
>>>>>
>>>>> this is the Jira issue (
>>>>> http://jira.springframework.org/browse/SPR-5991) Grabriel Axel posted
>>>>> it here some months ago (he opened the issue)
>>>>>
>>>>> The main problem with this task is that MapperMethods are not thread
>>>>> safe and that they hold an SqlSession. Enabling cache would require to
make
>>>>> some changes to MapperMethods. SqlSession should be passed as an argument
to
>>>>> execute instead to its constructor.
>>>>>
>>>>>  public MapperMethod(Method method, Configuration configuration) {
>>>>>     paramNames = new ArrayList<String>();
>>>>>     paramPositions = new ArrayList<Integer>();
>>>>>     ...
>>>>>
>>>>>   public Object execute(Object[] args, SqlSession sqlSession) {
>>>>>     Object result;
>>>>>     if (SqlCommandType.INSERT == type) {
>>>>>     ...
>>>>>
>>>>> And MapperProxy
>>>>>
>>>>>   public Object invoke(Object proxy, Method method, Object[] args)
>>>>> throws Throwable {
>>>>>     return new MapperMethod(method,
>>>>> sqlSession.getConfiguration()).execute(args, sqlSession);
>>>>>   }
>>>>>
>>>>>
>>>>> So for example Spring could have this code to use directly injected
>>>>> Mappers where SqlSession is got from spring's transaction context.
>>>>>
>>>>> private Map<Method, MapperMethod> mapperMethods = new
>>>>> ConcurrentHashMap<Method, MapperMethod>();
>>>>>
>>>>> public <T> T getMapper(final Class<T> type) {
>>>>>     // mimic iBATIS MapperProxy
>>>>>     return (T)
>>>>> java.lang.reflect.Proxy.newProxyInstance(type.getClassLoader(), new Class[]
>>>>> { type },
>>>>>             new InvocationHandler() {
>>>>>                 @Override
>>>>>                 public Object invoke(final Object proxy, final Method
>>>>> method, final Object[] args) throws Throwable {
>>>>>                     return execute(new SqlSessionCallback<T>()
{
>>>>>                         @Override
>>>>>                         public T doInSqlSession(SqlSession sqlSession)
>>>>> throws SQLException {
>>>>>                                // mimic iBATIS MapperProxy
>>>>>                             Class<?> type = method.getDeclaringClass();
>>>>>                                 if
>>>>> (!sqlSession.getConfiguration().hasMapper(type)) {
>>>>>                                 throw new BindingException("Type " +
>>>>> type + " is not known to the MapperRegistry.");
>>>>>                             }
>>>>>
>>>>>                             MapperMethod mm =
>>>>> mapperMethods.get(method);
>>>>>                             if (mm == null) {
>>>>>                                     mm = new MapperMethod(method,
>>>>> sqlSession.getConfiguration());
>>>>>                                     mapperMethods.put(method, mm);
>>>>>                                 } else {
>>>>>                                     logger.debug("Returning a cached
>>>>> mapper method");
>>>>>                                 }
>>>>>
>>>>>                                 return (T) mm.execute(args,
>>>>> sqlSession);
>>>>>                             }
>>>>>                         });
>>>>>                     }
>>>>>                 });
>>>>>     }
>>>>>
>>>>> Or maybe avoid using directly MapperMethods and access to them thought
>>>>> the "standard" api SqlSession.getConfiguration().getMapper(impl, sesion)
but
>>>>> in that case caching should be done inside.
>>>>>
>>>>> what do you think about this?
>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>

Mime
View raw message