Bitronix 3.0

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Bitronix 3.0

Brett Wooldridge-2
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge

Reply | Threaded
Open this post in threaded view
|

SV: Bitronix 3.0

Lars Arvidson

Great work Brett!

 

The new DiskJournal implementation looks very promising.

I’m looking forward to the release of 3.0.

 

Regards

Lars

 

Från: Brett Wooldridge [mailto:[hidden email]]
Skickat: den 3 december 2012 07:43
Till: [hidden email]
Ämne: [btm-user] Bitronix 3.0

 

Ludovic,

 

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

 

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

 

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

 

The difference in performance between the two is summarized as follows:

 

+--------------------------------+-------------+-------------+

|             Test               | Java Proxy  | Cglib Proxy | 

+--------------------------------+-------------+-------------+

| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |

+--------------------------------+-------------+-------------+

| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |

+--------------------------------+-------------+-------------+

 

As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

 

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

 

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

 

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

 

Regards,

Brett Wooldridge

 

Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Gérald Quintana
In reply to this post by Brett Wooldridge-2
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald

2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge


Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Brett Wooldridge-2
Hi Gerald,

I did experiment with creating a proxy instance pool, where PreparedStatements (for example) that were 'closed' were returned to the pool to be reused by another conn.prepareStatement() call.  It is much faster (object creation, not invocation), but it presented issues.  For example, our proxy basically has a 'delegate' inside, which is the real PreparedStatement (or Connection, or whatever is proxied).  However, we cannot control how the user maintains a reference to a PreparedStatement.  So, look at this scenario, first assuming no pooling...

PreparedStatement stmt = conn.prepareStatement(...);  // stmt is really our proxy
stmt.executeQuery();
stmt.close();  // statement is now closed
stmt.close();  // this should be a no-op
stmt.close();  // no matter how many times they call it, it should be harmless now

How consider the same thing using pooling ...

PreparedStatement stmt = conn.prepareStatement(...);
stmt.executeQuery();
stmt.close();  // this would return the stmt (proxy) to the pool
stmt.close();  // this is a no-op

// Now imagine another thread does a conn.prepareStatement() and receives the above proxy from the pool

stmt.close();  // OUCH!  This call actually just closed that other thread's PreparedStatement proxy

This might seem an unlikely scenario, and yet I will tell you that when I ran the proxy pooling code in our pre-production environment, it almost immediately blew up.  Why?  The ORM library we are using actually closes the same statement multiple times.  So, while possibly considered "incorrect", the fact that it could lead to closing some other random PreparedStatment would create a debugging nightmare for someone.  Basically, code that used to function for a user might stop working, and in possibly surprising ways.

In that face of that possibility, and the fact that it actually occurred in our pre-production environment, I ripped out the proxy pooling code.  Even with pooling, the performance was still slightly behind that of cglib-based proxies in terms of object creation.  However, cglib still wins hands-down in invocation performance.

Regards,
Brett

p.s. I'm not saying that pooling proxies in general is a bad idea, it's not, but in the case of proxying a stateful resource  it might be.



On Mon, Dec 3, 2012 at 6:12 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge



Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Brett Wooldridge-2
Gerald,

Sorry, I should have read more carefully, when you said "caching proxy generated classes" my brain read "instances".  Caching the Constructor of a generated proxy class and creating instances by calling newInstance() on the Constructor is interesting.  While it still won't match the cglib-proxy in performance it should perform much better (again for object construction) and will almost certainly eliminate the multiple instances created in perm gen.

Thanks for the tip, I'll try it out tomorrow.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:02 PM, Brett Wooldridge <[hidden email]> wrote:
Hi Gerald,

I did experiment with creating a proxy instance pool, where PreparedStatements (for example) that were 'closed' were returned to the pool to be reused by another conn.prepareStatement() call.  It is much faster (object creation, not invocation), but it presented issues.  For example, our proxy basically has a 'delegate' inside, which is the real PreparedStatement (or Connection, or whatever is proxied).  However, we cannot control how the user maintains a reference to a PreparedStatement.  So, look at this scenario, first assuming no pooling...

PreparedStatement stmt = conn.prepareStatement(...);  // stmt is really our proxy
stmt.executeQuery();
stmt.close();  // statement is now closed
stmt.close();  // this should be a no-op
stmt.close();  // no matter how many times they call it, it should be harmless now

How consider the same thing using pooling ...

PreparedStatement stmt = conn.prepareStatement(...);
stmt.executeQuery();
stmt.close();  // this would return the stmt (proxy) to the pool
stmt.close();  // this is a no-op

// Now imagine another thread does a conn.prepareStatement() and receives the above proxy from the pool

stmt.close();  // OUCH!  This call actually just closed that other thread's PreparedStatement proxy

This might seem an unlikely scenario, and yet I will tell you that when I ran the proxy pooling code in our pre-production environment, it almost immediately blew up.  Why?  The ORM library we are using actually closes the same statement multiple times.  So, while possibly considered "incorrect", the fact that it could lead to closing some other random PreparedStatment would create a debugging nightmare for someone.  Basically, code that used to function for a user might stop working, and in possibly surprising ways.

In that face of that possibility, and the fact that it actually occurred in our pre-production environment, I ripped out the proxy pooling code.  Even with pooling, the performance was still slightly behind that of cglib-based proxies in terms of object creation.  However, cglib still wins hands-down in invocation performance.

Regards,
Brett

p.s. I'm not saying that pooling proxies in general is a bad idea, it's not, but in the case of proxying a stateful resource  it might be.



On Mon, Dec 3, 2012 at 6:12 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge




Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Brett Wooldridge-2
Gerald,

Thanks for the reference to the code on caching the Proxy constructor, it works a treat.  The proxy instantiation overhead is now essentially identical regardless of which proxy type is used; both taking about 1 second on my box to create 1000 instances.  As noted, this does not affect (or improve) the actual method dispatch time, which remains around 8x for the cglib-based proxies, but it is a nice boost regardless.  I've checked the changes into my github fork.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:15 PM, Brett Wooldridge <[hidden email]> wrote:
Gerald,

Sorry, I should have read more carefully, when you said "caching proxy generated classes" my brain read "instances".  Caching the Constructor of a generated proxy class and creating instances by calling newInstance() on the Constructor is interesting.  While it still won't match the cglib-proxy in performance it should perform much better (again for object construction) and will almost certainly eliminate the multiple instances created in perm gen.

Thanks for the tip, I'll try it out tomorrow.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:02 PM, Brett Wooldridge <[hidden email]> wrote:
Hi Gerald,

I did experiment with creating a proxy instance pool, where PreparedStatements (for example) that were 'closed' were returned to the pool to be reused by another conn.prepareStatement() call.  It is much faster (object creation, not invocation), but it presented issues.  For example, our proxy basically has a 'delegate' inside, which is the real PreparedStatement (or Connection, or whatever is proxied).  However, we cannot control how the user maintains a reference to a PreparedStatement.  So, look at this scenario, first assuming no pooling...

PreparedStatement stmt = conn.prepareStatement(...);  // stmt is really our proxy
stmt.executeQuery();
stmt.close();  // statement is now closed
stmt.close();  // this should be a no-op
stmt.close();  // no matter how many times they call it, it should be harmless now

How consider the same thing using pooling ...

PreparedStatement stmt = conn.prepareStatement(...);
stmt.executeQuery();
stmt.close();  // this would return the stmt (proxy) to the pool
stmt.close();  // this is a no-op

// Now imagine another thread does a conn.prepareStatement() and receives the above proxy from the pool

stmt.close();  // OUCH!  This call actually just closed that other thread's PreparedStatement proxy

This might seem an unlikely scenario, and yet I will tell you that when I ran the proxy pooling code in our pre-production environment, it almost immediately blew up.  Why?  The ORM library we are using actually closes the same statement multiple times.  So, while possibly considered "incorrect", the fact that it could lead to closing some other random PreparedStatment would create a debugging nightmare for someone.  Basically, code that used to function for a user might stop working, and in possibly surprising ways.

In that face of that possibility, and the fact that it actually occurred in our pre-production environment, I ripped out the proxy pooling code.  Even with pooling, the performance was still slightly behind that of cglib-based proxies in terms of object creation.  However, cglib still wins hands-down in invocation performance.

Regards,
Brett

p.s. I'm not saying that pooling proxies in general is a bad idea, it's not, but in the case of proxying a stateful resource  it might be.



On Mon, Dec 3, 2012 at 6:12 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge





Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Gérald Quintana
Hi Brett,

Here are my own tests:
  • No proxy at all: 0,01s, 4s
  • Reflect proxy: 0,5s, 13s
  • Reflect proxy + Ctor cache: 0,05ms, 13s
  • Naive CGLib proxy: 0,15s, 16s
First number is proxy instantiation, second one is 1000 method invocations on it (all intercepted). The numbers are for 100000 iterations. Method invocation are 3 to 4 times slower with proxies on my dev box.

I bet CGLib attempt is not optimal, I to learn how to use it.

Thanks for you comments.
Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Gerald,

Thanks for the reference to the code on caching the Proxy constructor, it works a treat.  The proxy instantiation overhead is now essentially identical regardless of which proxy type is used; both taking about 1 second on my box to create 1000 instances.  As noted, this does not affect (or improve) the actual method dispatch time, which remains around 8x for the cglib-based proxies, but it is a nice boost regardless.  I've checked the changes into my github fork.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:15 PM, Brett Wooldridge <[hidden email]> wrote:
Gerald,

Sorry, I should have read more carefully, when you said "caching proxy generated classes" my brain read "instances".  Caching the Constructor of a generated proxy class and creating instances by calling newInstance() on the Constructor is interesting.  While it still won't match the cglib-proxy in performance it should perform much better (again for object construction) and will almost certainly eliminate the multiple instances created in perm gen.

Thanks for the tip, I'll try it out tomorrow.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:02 PM, Brett Wooldridge <[hidden email]> wrote:
Hi Gerald,

I did experiment with creating a proxy instance pool, where PreparedStatements (for example) that were 'closed' were returned to the pool to be reused by another conn.prepareStatement() call.  It is much faster (object creation, not invocation), but it presented issues.  For example, our proxy basically has a 'delegate' inside, which is the real PreparedStatement (or Connection, or whatever is proxied).  However, we cannot control how the user maintains a reference to a PreparedStatement.  So, look at this scenario, first assuming no pooling...

PreparedStatement stmt = conn.prepareStatement(...);  // stmt is really our proxy
stmt.executeQuery();
stmt.close();  // statement is now closed
stmt.close();  // this should be a no-op
stmt.close();  // no matter how many times they call it, it should be harmless now

How consider the same thing using pooling ...

PreparedStatement stmt = conn.prepareStatement(...);
stmt.executeQuery();
stmt.close();  // this would return the stmt (proxy) to the pool
stmt.close();  // this is a no-op

// Now imagine another thread does a conn.prepareStatement() and receives the above proxy from the pool

stmt.close();  // OUCH!  This call actually just closed that other thread's PreparedStatement proxy

This might seem an unlikely scenario, and yet I will tell you that when I ran the proxy pooling code in our pre-production environment, it almost immediately blew up.  Why?  The ORM library we are using actually closes the same statement multiple times.  So, while possibly considered "incorrect", the fact that it could lead to closing some other random PreparedStatment would create a debugging nightmare for someone.  Basically, code that used to function for a user might stop working, and in possibly surprising ways.

In that face of that possibility, and the fact that it actually occurred in our pre-production environment, I ripped out the proxy pooling code.  Even with pooling, the performance was still slightly behind that of cglib-based proxies in terms of object creation.  However, cglib still wins hands-down in invocation performance.

Regards,
Brett

p.s. I'm not saying that pooling proxies in general is a bad idea, it's not, but in the case of proxying a stateful resource  it might be.



On Mon, Dec 3, 2012 at 6:12 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge






Reply | Threaded
Open this post in threaded view
|

Re: Bitronix 3.0

Brett Wooldridge-2
Gerald,

The reason that cglib outperforms Java Proxy is exactly because not all invocations are intercepted.  Take for example, PreparedStatement.  PreparedStatement has more than 50 methods, however for tracking purposes Bitronix only needs to intercept close() and isClosed().  A Java Proxy intercepts all methods on the proxied interface, even those we're not interested in, and requires reflection to dispatch them.

In contrast, using cglib we are able to generate a proxy class that intercepts only the two methods we are interested in, with the others begin dispatched [almost] directly to the underlying delegate.  Intercepted methods are invoked via reflection so performance of those is the same as a Java Proxy.  But there are only two such methods, so it's a big win.

You can look at JdbcCglibProxyFactory to see how it's done, but here's a quick overview if you're interested:

First we get the list of interfaces we want to proxy (eg. PreparedStatement and it's inherited interfaces):

Set<Class<?>> interfaces = ClassLoaderUtils.getAllInterfaces(PreparedStatement.class);

We create a cglib Enhancer and set the interfaces into it ...

Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(interfaces.toArray(new Class<?>[0]));

Enhancer takes a CallbackFilter that tells it which methods to intercept what do with with them:

enhancer.setCallbackFilter(new InterceptorFilter(...));

Our InterceptorFilter's accept() method returns an integer which is a index that selects a Callback type from an array of callbacks.  We have two Callbacks, FastDispatcher (which implements LazyLoader) and Interceptor (which implements MethodInterceptor).  We set them like this:

enhancer.setCallbackTypes(new Class[] {FastDispatcher.class, Interceptor.class} );

All our InterceptorFilter needs to do is return '0' for methods we don't care about (i.e. use FastDispatcher), or returns '1' for methods we do care about (i.e. use Interceptor).

Once those things are set, we generate a proxy class:

proxyPreparedStatementClass = enhancer.createClass();

To create a proxy instance, we do:

PreparedStatement statementCglibProxy = proxyPreparedStatementClass.newInstance();
((Factory) statementCglibProxy).setCallbacks(new Callback[] { fastDispatcher, interceptor });
return statementCglibProxy;

Where fastDispatcher is an instance of FastDispatcher (just returns the real PreparedStatement), and interceptor is an instance of Interceptor (which calls our overridden implementations via standard reflection).

Regards,
Brett



On Mon, Dec 3, 2012 at 11:03 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

Here are my own tests:
  • No proxy at all: 0,01s, 4s
  • Reflect proxy: 0,5s, 13s
  • Reflect proxy + Ctor cache: 0,05ms, 13s
  • Naive CGLib proxy: 0,15s, 16s
First number is proxy instantiation, second one is 1000 method invocations on it (all intercepted). The numbers are for 100000 iterations. Method invocation are 3 to 4 times slower with proxies on my dev box.

I bet CGLib attempt is not optimal, I to learn how to use it.

Thanks for you comments.

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Gerald,

Thanks for the reference to the code on caching the Proxy constructor, it works a treat.  The proxy instantiation overhead is now essentially identical regardless of which proxy type is used; both taking about 1 second on my box to create 1000 instances.  As noted, this does not affect (or improve) the actual method dispatch time, which remains around 8x for the cglib-based proxies, but it is a nice boost regardless.  I've checked the changes into my github fork.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:15 PM, Brett Wooldridge <[hidden email]> wrote:
Gerald,

Sorry, I should have read more carefully, when you said "caching proxy generated classes" my brain read "instances".  Caching the Constructor of a generated proxy class and creating instances by calling newInstance() on the Constructor is interesting.  While it still won't match the cglib-proxy in performance it should perform much better (again for object construction) and will almost certainly eliminate the multiple instances created in perm gen.

Thanks for the tip, I'll try it out tomorrow.

Regards,
Brett



On Mon, Dec 3, 2012 at 7:02 PM, Brett Wooldridge <[hidden email]> wrote:
Hi Gerald,

I did experiment with creating a proxy instance pool, where PreparedStatements (for example) that were 'closed' were returned to the pool to be reused by another conn.prepareStatement() call.  It is much faster (object creation, not invocation), but it presented issues.  For example, our proxy basically has a 'delegate' inside, which is the real PreparedStatement (or Connection, or whatever is proxied).  However, we cannot control how the user maintains a reference to a PreparedStatement.  So, look at this scenario, first assuming no pooling...

PreparedStatement stmt = conn.prepareStatement(...);  // stmt is really our proxy
stmt.executeQuery();
stmt.close();  // statement is now closed
stmt.close();  // this should be a no-op
stmt.close();  // no matter how many times they call it, it should be harmless now

How consider the same thing using pooling ...

PreparedStatement stmt = conn.prepareStatement(...);
stmt.executeQuery();
stmt.close();  // this would return the stmt (proxy) to the pool
stmt.close();  // this is a no-op

// Now imagine another thread does a conn.prepareStatement() and receives the above proxy from the pool

stmt.close();  // OUCH!  This call actually just closed that other thread's PreparedStatement proxy

This might seem an unlikely scenario, and yet I will tell you that when I ran the proxy pooling code in our pre-production environment, it almost immediately blew up.  Why?  The ORM library we are using actually closes the same statement multiple times.  So, while possibly considered "incorrect", the fact that it could lead to closing some other random PreparedStatment would create a debugging nightmare for someone.  Basically, code that used to function for a user might stop working, and in possibly surprising ways.

In that face of that possibility, and the fact that it actually occurred in our pre-production environment, I ripped out the proxy pooling code.  Even with pooling, the performance was still slightly behind that of cglib-based proxies in terms of object creation.  However, cglib still wins hands-down in invocation performance.

Regards,
Brett

p.s. I'm not saying that pooling proxies in general is a bad idea, it's not, but in the case of proxying a stateful resource  it might be.



On Mon, Dec 3, 2012 at 6:12 PM, Gérald Quintana <[hidden email]> wrote:
Hi Brett,

I've been dealing with same need on different project (http://code.google.com/p/javasimon/source/browse/branches/feature-jdbcproxy/jdbc4/src/main/java/org/javasimon/jdbc/JdbcProxyFactoryFactory.java). But I didn't went that far, so your analysis is very interesting to me. 

My only conclusion was Proxy.newProxyInstance was slow, I found a small optimization (caching proxy generated classes) but didn't test it yet. Look here:
http://www.java2s.com/Code/Java/Reflection/Ahighperformancefactoryfordynamicproxyobjects.htm

Did you try that?

Gérald


2012/12/3 Brett Wooldridge <[hidden email]>
Ludovic,

I've been working on the JDBC proxies to try to find a happy medium between our two concerns.  Here's what I've done:

Proxies are now created by a factory class (JdbcProxyFactory).  There are two JdbcProxyFactory implementations:

The JdbcJavaProxyFactory uses standard Java Proxies, like bitronix 2.1.x, however they have been completely rewritten for simplicity and performance.  The JdbcCglibProxyFactory uses the cglib library to create proxy classes at runtime; surprisingly though they are compiled (at runtime) classes, they share implementation with the rewritten Java Proxy-based classes.  The result is that only the factory implementation is different.  Users who use Hibernate and therefore already have cglib, or users who include cglib in their classpath will be able to take advantage of these proxies.

The difference in performance between the two is summarized as follows:

+--------------------------------+-------------+-------------+
|             Test               | Java Proxy  | Cglib Proxy | 
+--------------------------------+-------------+-------------+
| conn.prepareStatement() x 1000 |  8.061 sec  |  1.041 sec  |
+--------------------------------+-------------+-------------+
| pstmt.setXXX() x 250,000 calls |    128 ms   |     20 ms   |
+--------------------------------+-------------+-------------+
 
As you can see, the Cglib proxies perform about 8x faster.  Additionally, the number of temporary classes created by the JVM in perm gen is at least three orders of magnitudes lower.  For example, with the 1000 calls above, when using the Java Proxy class the JVM will generate 1000 corresponding temporary classes in perm gen.  If you change that loop to 20,000 and store the resulting PreparedStatements in an array (so they can't be immediately garbage collected), you will get an OutOfMemory error in perm gen.  This was always true of bitronix 2.1.x proxies as well.

However, the Cglib-based proxy factory generates a single actual permanent Java class (a proxy PreparedStatement) regardless of how many statements are prepared.  This has substantial impact on not only memory footprint but also garbage collection overhead.

There is a new configuration property called 'bitronix.tm.jdbcProxyFactoryClass' whose default value is 'auto'.  If the presence of cglib is detected at runtime, the JdbcCglibProxyFactory will be used.  If cglib is not present, standard Java Proxies will be used.  Of course, if a user ever encounters any weirdness with the cglib-based proxies, they can set 'bitronix.tm.JdbcProxyFactoryClass' to 'bitronix.tm.resource.jdbc.proxy.JdbcJavaProxyFactory' to force Java Proxy-based proxies.  But I would advocate the use of the cglib-based proxies (and therefore 'auto' as a default).

With this change, the jdbc3-stubs, jdbc4-stubs, and jdbc4 modules have disappeared.  Currently the changes are checked into my fork (https://github.com/brettwooldridge/bitronix-hp) but I will try to move them over to the BTM-2.2 branch on codehaus this week.

Regards,
Brett Wooldridge