Repository: incubator-systemml
Updated Branches:
  refs/heads/master 0bf27c2a8 -> 9a77b7bcc


[SYSTEMML-1291] Size bounding codegen plan and class caches (LRU, 1024)

The code generator uses a plan cache to reuse generated classes across
DAGs and during DAG recompilation in order to significantly reduce javac
and JIT compilation overheads. However, so far this plan cache was
unbounded in size, which can lead to various out-of-memory situations as
the garbage collector cannot unload unused classes. This patch
establishes a size-bounded plan cache, which maintains usage order and
evicts the least-recently-used (LRU) plan and corresponding entries from
the class cache, if the maximum number of plans (1024) is exceeded. 

Note that we use a hand-crafted eviction policy instead of soft
references to maintain consistency across plan and class caches and to
allow for better debugging.


Project: http://git-wip-us.apache.org/repos/asf/incubator-systemml/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-systemml/commit/9a77b7bc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-systemml/tree/9a77b7bc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-systemml/diff/9a77b7bc

Branch: refs/heads/master
Commit: 9a77b7bcc0036c97a36d38b4b3e696dd28cc9598
Parents: 0bf27c2
Author: Matthias Boehm <mboe...@gmail.com>
Authored: Thu Mar 30 00:45:07 2017 -0700
Committer: Matthias Boehm <mboe...@gmail.com>
Committed: Thu Mar 30 00:47:36 2017 -0700

----------------------------------------------------------------------
 .../java/org/apache/sysml/api/DMLScript.java    |   4 +-
 .../sysml/hops/codegen/SpoofCompiler.java       | 114 +++++++++++++------
 .../sysml/runtime/codegen/CodegenUtils.java     |  10 ++
 3 files changed, 93 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-systemml/blob/9a77b7bc/src/main/java/org/apache/sysml/api/DMLScript.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/sysml/api/DMLScript.java 
b/src/main/java/org/apache/sysml/api/DMLScript.java
index c04c321..b1d99a7 100644
--- a/src/main/java/org/apache/sysml/api/DMLScript.java
+++ b/src/main/java/org/apache/sysml/api/DMLScript.java
@@ -57,7 +57,7 @@ import org.apache.sysml.hops.HopsException;
 import org.apache.sysml.hops.OptimizerUtils;
 import org.apache.sysml.hops.OptimizerUtils.OptimizationLevel;
 import org.apache.sysml.hops.codegen.SpoofCompiler;
-import org.apache.sysml.hops.codegen.SpoofCompiler.PlanCache;
+import org.apache.sysml.hops.codegen.SpoofCompiler.PlanCachePolicy;
 import org.apache.sysml.hops.globalopt.GlobalOptimizerWrapper;
 import org.apache.sysml.lops.Lop;
 import org.apache.sysml.lops.LopsException;
@@ -597,7 +597,7 @@ public class DMLScript
 
                //Step 5.1: Generate code for the rewrited Hop dags 
                if( dmlconf.getBooleanValue(DMLConfig.CODEGEN) ){
-                       SpoofCompiler.PLAN_CACHE_POLICY = PlanCache.getPolicy(
+                       SpoofCompiler.PLAN_CACHE_POLICY = PlanCachePolicy.get(
                                        
dmlconf.getBooleanValue(DMLConfig.CODEGEN_PLANCACHE),
                                        
dmlconf.getIntValue(DMLConfig.CODEGEN_LITERALS)==2);
                        dmlt.codgenHopsDAG(prog);

http://git-wip-us.apache.org/repos/asf/incubator-systemml/blob/9a77b7bc/src/main/java/org/apache/sysml/hops/codegen/SpoofCompiler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/sysml/hops/codegen/SpoofCompiler.java 
b/src/main/java/org/apache/sysml/hops/codegen/SpoofCompiler.java
index 071b03b..011e4d2 100644
--- a/src/main/java/org/apache/sysml/hops/codegen/SpoofCompiler.java
+++ b/src/main/java/org/apache/sysml/hops/codegen/SpoofCompiler.java
@@ -26,7 +26,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -84,9 +83,10 @@ public class SpoofCompiler
        //internal configuration flags
        public static boolean LDEBUG = false;
        public static final boolean RECOMPILE_CODEGEN = true;
-       public static PlanCache PLAN_CACHE_POLICY = PlanCache.CSLH;
-       public static final PlanSelector PLAN_SEL_POLICY = 
PlanSelector.FUSE_ALL; 
        public static final boolean PRUNE_REDUNDANT_PLANS = true;
+       public static PlanCachePolicy PLAN_CACHE_POLICY = PlanCachePolicy.CSLH;
+       public static final int PLAN_CACHE_SIZE = 1024; //max 1K classes 
+       public static final PlanSelector PLAN_SEL_POLICY = 
PlanSelector.FUSE_ALL; 
        
        public enum PlanSelector {
                FUSE_ALL,             //maximal fusion, possible w/ redundant 
compute
@@ -94,19 +94,20 @@ public class SpoofCompiler
                FUSE_COST_BASED,      //cost-based decision on materialization 
points
        }
 
-       public enum PlanCache {
+       public enum PlanCachePolicy {
                CONSTANT, //plan cache, with always compile literals
                CSLH,     //plan cache, with context-sensitive literal 
replacement heuristic
                NONE;     //no plan cache
                
-               public static PlanCache getPolicy(boolean planCache, boolean 
compileLiterals) {
+               public static PlanCachePolicy get(boolean planCache, boolean 
compileLiterals) {
                        return !planCache ? NONE : compileLiterals ? CONSTANT : 
CSLH;
                }
        }
        
        //plan cache for cplan->compiled source to avoid unnecessary 
codegen/source code compile
        //for equal operators from (1) different hop dags and (2) repeated 
recompilation 
-       private static ConcurrentHashMap<CNode, Class<?>> planCache = new 
ConcurrentHashMap<CNode, Class<?>>();
+       //note: if PLAN_CACHE_SIZE is exceeded, we evict the 
least-recently-used plan (LRU policy)
+       private static final PlanCache planCache = new 
PlanCache(PLAN_CACHE_SIZE);
        
        private static ProgramRewriter rewriteCSE = new ProgramRewriter(
                        new RewriteCommonSubexpressionElimination(true),
@@ -204,13 +205,6 @@ public class SpoofCompiler
                return optimize(new ArrayList<Hop>(Arrays.asList(root)), 
recompile).get(0);
        }
        
-       public static void cleanupCodeGenerator() {
-               if( PLAN_CACHE_POLICY != PlanCache.NONE ) {
-                       CodegenUtils.clearClassCache(); //class cache
-                       planCache.clear(); //plan cache
-               }
-       }
-       
        /**
         * Main interface of sum-product optimizer, statement block dag.
         * 
@@ -231,7 +225,7 @@ public class SpoofCompiler
                try
                {
                        //context-sensitive literal replacement (only integers 
during recompile)
-                       boolean compileLiterals = 
(PLAN_CACHE_POLICY==PlanCache.CONSTANT) || !recompile;
+                       boolean compileLiterals = 
(PLAN_CACHE_POLICY==PlanCachePolicy.CONSTANT) || !recompile;
                        
                        //construct codegen plans
                        HashMap<Long, Pair<Hop[],CNodeTpl>>  cplans = 
constructCPlans(roots, compileLiterals);
@@ -247,10 +241,12 @@ public class SpoofCompiler
                        
                        //source code generation for all cplans
                        HashMap<Long, Pair<Hop[],Class<?>>> clas = new 
HashMap<Long, Pair<Hop[],Class<?>>>();
-                       for( Entry<Long, Pair<Hop[],CNodeTpl>> cplan : 
cplans.entrySet() ) {
+                       for( Entry<Long, Pair<Hop[],CNodeTpl>> cplan : 
cplans.entrySet() ) 
+                       {
                                Pair<Hop[],CNodeTpl> tmp = cplan.getValue();
+                               Class<?> cla = 
planCache.getPlan(tmp.getValue());
                                
-                               if( PLAN_CACHE_POLICY==PlanCache.NONE || 
!planCache.containsKey(tmp.getValue()) ) {
+                               if( cla == null ) {
                                        //generate java source code
                                        String src = 
tmp.getValue().codegen(false);
                                        
@@ -266,17 +262,19 @@ public class SpoofCompiler
                                        }
                                        
                                        //compile generated java source code
-                                       Class<?> cla = 
CodegenUtils.compileClass(tmp.getValue().getClassname(), src);
-                                       planCache.put(tmp.getValue(), cla);
+                                       cla = 
CodegenUtils.compileClass(tmp.getValue().getClassname(), src);
+                                       
+                                       //maintain plan cache
+                                       if( 
PLAN_CACHE_POLICY!=PlanCachePolicy.NONE )
+                                               
planCache.putPlan(tmp.getValue(), cla);
                                }
                                else if( LDEBUG || DMLScript.STATISTICS ) {
                                        
Statistics.incrementCodegenPlanCacheHits();
                                }
                                
-                               Class<?> cla = planCache.get(tmp.getValue());
+                               //make class available and maintain hits
                                if(cla != null)
                                        clas.put(cplan.getKey(), new 
Pair<Hop[],Class<?>>(tmp.getKey(),cla));
-                               
                                if( LDEBUG || DMLScript.STATISTICS )
                                        
Statistics.incrementCodegenPlanCacheTotal();
                        }
@@ -308,6 +306,30 @@ public class SpoofCompiler
                return ret;
        }
 
+       public static void cleanupCodeGenerator() {
+               if( PLAN_CACHE_POLICY != PlanCachePolicy.NONE ) {
+                       CodegenUtils.clearClassCache(); //class cache
+                       planCache.clear(); //plan cache
+               }
+       }
+       
+       /**
+        * Factory method for alternative plan selection policies.
+        * 
+        * @return plan selector
+        */
+       public static PlanSelection createPlanSelector() {
+               switch( PLAN_SEL_POLICY ) {
+                       case FUSE_ALL: 
+                               return new PlanSelectionFuseAll();
+                       case FUSE_NO_REDUNDANCY: 
+                               return new PlanSelectionFuseNoRedundancy();
+                       case FUSE_COST_BASED:
+                       default:        
+                               throw new RuntimeException("Unsupported "
+                                       + "plan selector: "+PLAN_SEL_POLICY);
+               }
+       }
        
        ////////////////////
        // Codegen plan construction
@@ -583,22 +605,48 @@ public class SpoofCompiler
                }
                return ret;
        }
-
+       
        /**
-        * Factory method for alternative plan selection policies.
+        * This plan cache maps CPlans to compiled and loaded classes in order
+        * to reduce javac and JIT compilation overhead. It uses a simple LRU 
+        * eviction policy if the maximum number of entries is exceeded. In 
case 
+        * of evictions, this cache also triggers the eviction of corresponding 
+        * class cache entries (1:N). 
+        * <p>
+        * Note: The JVM is free to garbage collect and unload classes that are 
no
+        * longer referenced.
         * 
-        * @return plan selector
         */
-       public static PlanSelection createPlanSelector() {
-               switch( PLAN_SEL_POLICY ) {
-                       case FUSE_ALL: 
-                               return new PlanSelectionFuseAll();
-                       case FUSE_NO_REDUNDANCY: 
-                               return new PlanSelectionFuseNoRedundancy();
-                       case FUSE_COST_BASED:
-                       default:        
-                               throw new RuntimeException("Unsupported "
-                                       + "plan selector: "+PLAN_SEL_POLICY);
+       private static class PlanCache {
+               private final LinkedHashMap<CNode, Class<?>> _plans;
+               private final int _maxSize;
+               
+               public PlanCache(int maxSize) {
+                        _plans = new LinkedHashMap<CNode, Class<?>>(); 
+                        _maxSize = maxSize;
+               }
+               
+               public synchronized Class<?> getPlan(CNode key) {
+                       //constant time get and maintain usage order
+                       Class<?> value = _plans.remove(key);
+                       if( value != null ) 
+                               _plans.put(key, value); 
+                       return value;
+               }
+               
+               public synchronized void putPlan(CNode key, Class<?> value) {
+                       if( _plans.size() >= _maxSize ) {
+                               //remove least recently used (i.e., first) entry
+                               Iterator<Entry<CNode, Class<?>>> iter = 
_plans.entrySet().iterator();
+                               Class<?> rmCla = iter.next().getValue();
+                               CodegenUtils.clearClassCache(rmCla); //class 
cache
+                               iter.remove(); //plan cache
+                       }
+                       _plans.put(key, value);
+               }
+               
+               public synchronized void clear() {
+                       _plans.clear();
                }
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-systemml/blob/9a77b7bc/src/main/java/org/apache/sysml/runtime/codegen/CodegenUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/sysml/runtime/codegen/CodegenUtils.java 
b/src/main/java/org/apache/sysml/runtime/codegen/CodegenUtils.java
index 7a60a80..6908d7d 100644
--- a/src/main/java/org/apache/sysml/runtime/codegen/CodegenUtils.java
+++ b/src/main/java/org/apache/sysml/runtime/codegen/CodegenUtils.java
@@ -25,7 +25,9 @@ import java.io.InputStream;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.tools.Diagnostic;
@@ -233,6 +235,14 @@ public class CodegenUtils
                _cache.clear();
        }
        
+       public static void clearClassCache(Class<?> cla) {
+               //one-pass, in-place filtering of class cache
+               Iterator<Entry<String,Class<?>>> iter = 
_cache.entrySet().iterator();
+               while( iter.hasNext() )
+                       if( iter.next().getValue()==cla )
+                               iter.remove();
+       }
+       
        private static void createWorkingDir() throws DMLRuntimeException  {
                if( _workingDir != null )
                        return;

Reply via email to