Proposal for performance changes
belaban Jan 4, 2006 3:18 AM[posted by Bela on behalf of Brian Dueck]
Optimize performance of Fqn.toString()
======================================
org.jboss.cache.Fqn
- added optimization to set stringRepresentation during construction for
some constructors (avoids unnecessarily looping through all elements in toString() for deeply nested Fqn's)
General performance and concurrency improvements for TreeNode
=============================================================
org.jboss.cache.TreeNode
- changed dataLock to Object() since none of the ReentranLock methods were
being used - we're just doing synchronize() on it.
- improved concurrency of initialization and access of children
- children()
- improved concurrency by avoiding unnecessary synchronized() calls
- initialize children of root node (Fqn.isRoot()==true) node to
ConcurrentHashMap instead of ConcurrentReaderHashMap
to allow for greater concurrency of modification of nodes just off
the root
- getOrCreateChild() & createChild() methods
- reworked to avoid making unnecessary synchronized(childrenMutex) calls
- the strategy is to only lock when we don't find the child
General performance and concurrency improvements for TransactionEntry
=====================================================================
org.jboss.cache.TransactionEntry
- changed TransactionEntry.locks from List based implementation to
ConcurrentReaderHashMap. This avoids expensive linear scanning calls
to locks.contains() in addLock() and also improves concurrency by
avoiding blocking synchronized() calls where most of the access to
locks is read-only. I'm using ConcurrentReaderHashMap here more as
a Set than a Map since there is no value to store. We're really just
interested in efficient searches in locks and highly concurrent reads.
- I'm not sure of the impact of this change on
org.jboss.cache.interceptors.OptimisticLockingInterceptor and
org.jboss.cache.interceptors.PessimisticLockInterceptor
which might be relying on some implementation specifics of the order of
insertion of entries into what getLocks() returns. Seems to me though
that if they do have some sort of dependency on ordering that there
should be a better way to deal with this...
- this change impacts one public method - getLocks() which previously
returned a java.util.List and now returns a java.util.Set. Only one test
is affected by this (org.jboss.cache.cache.optimistic.OpLockingInterceptorTest)
which depends on List.get(int) method that isn't available in Set.
General performance and concurrency improvements for TransactionTable
=====================================================================
org.jboss.cache.TransactionTable
- changed TransactionTable.tx_map and TransactionTable.txs to
ConcurrentHashMap to allow for concurrent writers. Since there is only
one TransactionTable within the TreeCache any thread participating in
a transaction will need to compete for read and write access.
General performance and concurrency improvements for TreeCache
==============================================================
org.jboss.cache.TreeCache
- getCurrentTransaction()
- removed unnecessary synchronized(tx_table) call as TransactionTable
handles synchronization by virtue of its ConcurrentHashMap implementation
Improve performance and concurrency of handling of TreeCacheListeners
=====================================================================
org.jboss.cache.TreeCache
- added concurrency optimizations to differentiate how TreeCache handles
TreeCacheListeners
- the root problem is that calling ConcurrentHashMap.keySet().iterator()
causes synchronization across the ConcurrentHashMap instance. From then on
using the iterator is highly concurrent and efficient, but obtaining the
iterator involves expensive locking. Look at ConcurrentHashMap.HashIterator.HashIterator()
to see the underlying issue.
- when you couple the above with the fact that the number of listeners
in the cache is typically very low (normally just 1 for the eviction listener)
this adds up to a *lot* of overhead and locking each time the TreeCache
must notify a listener, and for the most part that listener is only the
EvictionListener
- here's the changes I made to solve the problem:
- added TreeCache.setEvictionListener() and modified
org.jboss.cache.eviction.RegionManager to call
this vs. TreeCache.addTreeCacheListener. setEvictionListener stores the
TreeCacheListener in TreeCache .evictionPolicyListener, not the listeners
Map
- added TreeCache.hasListeners private boolean variable that's used to see
if listeners exist before trying to iterate
- modified the TreeCache.notifyXXX() methods to use
TreeCache.evictionPolicyListener and hasListeners
Re-added ability to filter eviction events (performance & functionality)
========================================================================
- In my scenario using JBossCache, I have certain subtrees in the cache that must be treated
atomically from an eviction perspective. i.e. I can't allow just one branch of the subtree
to be evicted. The whole subtree needs to be evicted at once, not just piece by piece.
In JBoss 1.2.3 and prior, it was possible to support this scenario by implementing my own
eviction policy (CascadingLRUPolicy) by subclassing LRUPolicy and overriding the evict() method.
Since my "atomic subtrees" always started just off the cache root so I just
ignored any evict() calls except where Fqn.size() == 1.
I then made a further optimization by overriding the TreeCacheListener methods implemented on
LRUPolicy to filter out any eviction events prior to them getting inserted into the bounded
buffer. The rational was that since I wasn't interested in evicting certain nodes,
I could save a bunch of time by simply causing JBossCache to ignore those events in the
first place.
The result was a big jump in performance and scalability in my tests.
While it is still possible to implement the evict() portion of my CascadingLRUPolicy, it looks
like some refactoring and redesign of how eviction policies and the region manager
interact makes it impossible to implement the filtering optimization.
So here's what I've done to re-add the functionality based on the current JBoss 1.2.4 (head):
org.jboss.cache.eviction.EvictionPolicy
- added a new method called EvictionPolicy.canIgnoreEvent(Fqn)
- canIgnoreEvent is called by RegionManager.EvictionTreeCacheListener prior to
adding the event to the region event buffer.
- if canIgnoreEvent returns true, the event is not added to the region event buffer
- if canIgnoreEvent returns false (default), the event is added to the region event
buffer as normal
org.jboss.cache.eviction.RegionManager
- added calls to EvictionPolicy.canIgnoreEvent in EvictionTreeCacheListener and
AOPEvictionTreeCacheListener
org.jboss.cache.eviction.BaseEvictionPolicy
- provide default implementation of canIgnoreEvent that always returns false