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
 
    