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;