[SYSTEMML-1988] JMLC API extension for cloning prepared scripts In complex applications and micro services, the need to reuse prepared scripts in a thread-local manner in order to amortize the compilation overhead, creates sometimes an unnecessary burden. Without dynamic recompilation, this is unnecessary because a single compiled program can be shared across concurrent invocations of PreparedScript.execute. This patch extends the JMLC API by cloning functionality, which allows to statically initialize a single prepared script and create cheap shallow copies of the script's program and meta data.
Project: http://git-wip-us.apache.org/repos/asf/systemml/repo Commit: http://git-wip-us.apache.org/repos/asf/systemml/commit/f7fe4342 Tree: http://git-wip-us.apache.org/repos/asf/systemml/tree/f7fe4342 Diff: http://git-wip-us.apache.org/repos/asf/systemml/diff/f7fe4342 Branch: refs/heads/master Commit: f7fe4342005ec0da383f359b70fbab48a25dff7a Parents: e888cce Author: Matthias Boehm <mboe...@gmail.com> Authored: Sat Nov 4 23:33:43 2017 -0700 Committer: Matthias Boehm <mboe...@gmail.com> Committed: Sun Nov 5 14:27:44 2017 -0800 ---------------------------------------------------------------------- .../org/apache/sysml/api/jmlc/Connection.java | 15 +++ .../apache/sysml/api/jmlc/PreparedScript.java | 37 ++++++ .../jmlc/JMLCClonedPreparedScriptTest.java | 113 +++++++++++++++++++ .../functions/jmlc/ZPackageSuite.java | 2 + 4 files changed, 167 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/systemml/blob/f7fe4342/src/main/java/org/apache/sysml/api/jmlc/Connection.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/sysml/api/jmlc/Connection.java b/src/main/java/org/apache/sysml/api/jmlc/Connection.java index e96e0aa..2568977 100644 --- a/src/main/java/org/apache/sysml/api/jmlc/Connection.java +++ b/src/main/java/org/apache/sysml/api/jmlc/Connection.java @@ -161,6 +161,21 @@ public class Connection implements Closeable * @param script string representing the DML or PyDML script * @param inputs string array of input variables to register * @param outputs string array of output variables to register + * @return PreparedScript object representing the precompiled script + * @throws DMLException if DMLException occurs + */ + public PreparedScript prepareScript( String script, String[] inputs, String[] outputs) + throws DMLException + { + return prepareScript(script, inputs, outputs, false); + } + + /** + * Prepares (precompiles) a script and registers input and output variables. + * + * @param script string representing the DML or PyDML script + * @param inputs string array of input variables to register + * @param outputs string array of output variables to register * @param parsePyDML {@code true} if PyDML, {@code false} if DML * @return PreparedScript object representing the precompiled script * @throws DMLException if DMLException occurs http://git-wip-us.apache.org/repos/asf/systemml/blob/f7fe4342/src/main/java/org/apache/sysml/api/jmlc/PreparedScript.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/sysml/api/jmlc/PreparedScript.java b/src/main/java/org/apache/sysml/api/jmlc/PreparedScript.java index c712bb2..c23ef92 100644 --- a/src/main/java/org/apache/sysml/api/jmlc/PreparedScript.java +++ b/src/main/java/org/apache/sysml/api/jmlc/PreparedScript.java @@ -23,7 +23,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map.Entry; +import org.apache.commons.lang.NotImplementedException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.sysml.api.DMLException; @@ -75,6 +77,21 @@ public class PreparedScript private final DMLConfig _dmlconf; private final CompilerConfig _cconf; + private PreparedScript(PreparedScript that) { + //shallow copy, except for a separate symbol table + //and related meta data of reused inputs + _prog = that._prog; + _vars = new LocalVariableMap(); + for(Entry<String, Data> e : that._vars.entrySet()) + _vars.put(e.getKey(), e.getValue()); + _vars.setRegisteredOutputs(that._outVarnames); + _inVarnames = that._inVarnames; + _outVarnames = that._outVarnames; + _inVarReuse = new HashMap<>(that._inVarReuse); + _dmlconf = that._dmlconf; + _cconf = that._cconf; + } + /** * Meant to be invoked only from Connection. * @@ -481,4 +498,24 @@ public class PreparedScript } } } + + /** + * Creates a cloned instance of the prepared script, which + * allows for concurrent execution without side effects. + * + * @param deep indicator if a deep copy needs to be created; + * if false, only a shallow (i.e., by reference) copy of the + * program and read-only meta data is created. + * @return an equivalent prepared script + */ + public PreparedScript clone(boolean deep) { + if( deep ) + throw new NotImplementedException(); + return new PreparedScript(this); + } + + @Override + public Object clone() { + return clone(true); + } } http://git-wip-us.apache.org/repos/asf/systemml/blob/f7fe4342/src/test/java/org/apache/sysml/test/integration/functions/jmlc/JMLCClonedPreparedScriptTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/sysml/test/integration/functions/jmlc/JMLCClonedPreparedScriptTest.java b/src/test/java/org/apache/sysml/test/integration/functions/jmlc/JMLCClonedPreparedScriptTest.java new file mode 100644 index 0000000..d0667e0 --- /dev/null +++ b/src/test/java/org/apache/sysml/test/integration/functions/jmlc/JMLCClonedPreparedScriptTest.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sysml.test.integration.functions.jmlc; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.Assert; +import org.junit.Test; +import org.apache.sysml.api.DMLException; +import org.apache.sysml.api.DMLScript; +import org.apache.sysml.api.jmlc.Connection; +import org.apache.sysml.api.jmlc.PreparedScript; +import org.apache.sysml.runtime.controlprogram.parfor.stat.InfrastructureAnalyzer; +import org.apache.sysml.test.integration.AutomatedTestBase; +import org.apache.sysml.utils.Statistics; + +public class JMLCClonedPreparedScriptTest extends AutomatedTestBase +{ + @Override + public void setUp() { + //do nothing + } + + @Test + public void testSinglePreparedScript128() throws IOException { + runJMLCClonedTest(128, false); + } + + @Test + public void testClonedPreparedScript128() throws IOException { + runJMLCClonedTest(128, true); + } + + private void runJMLCClonedTest(int num, boolean clone) + throws IOException + { + int k = InfrastructureAnalyzer.getLocalParallelism(); + + boolean failed = false; + try( Connection conn = new Connection() ) { + String script = + " X = matrix(7, 10, 10);" + + "R = matrix(0, 10, 1)" + + "parfor(i in 1:nrow(X))" + + " R[i,] = sum(X[i,])" + + "out = sum(R)" + + "write(out, 'tmp/out')"; + DMLScript.STATISTICS = true; + Statistics.reset(); + PreparedScript pscript = conn.prepareScript( + script, new String[]{}, new String[]{"out"}, false); + + ExecutorService pool = Executors.newFixedThreadPool(k); + ArrayList<JMLCTask> tasks = new ArrayList<>(); + for(int i=0; i<num; i++) + tasks.add(new JMLCTask(pscript, clone)); + List<Future<Double>> taskrets = pool.invokeAll(tasks); + for(Future<Double> ret : taskrets) + if( ret.get() != 700 ) + throw new RuntimeException("wrong results: "+ret.get()); + pool.shutdown(); + } + catch(Exception ex) { + failed = true; + } + + //check expected failure + Assert.assertTrue(failed==!clone || k==1); + } + + private static class JMLCTask implements Callable<Double> + { + private final PreparedScript _pscript; + private final boolean _clone; + + protected JMLCTask(PreparedScript pscript, boolean clone) { + _pscript = pscript; + _clone = clone; + } + + @Override + public Double call() throws DMLException + { + if( _clone ) + return _pscript.clone(false).executeScript().getDouble("out"); + else + return _pscript.executeScript().getDouble("out"); + } + } +} http://git-wip-us.apache.org/repos/asf/systemml/blob/f7fe4342/src/test_suites/java/org/apache/sysml/test/integration/functions/jmlc/ZPackageSuite.java ---------------------------------------------------------------------- diff --git a/src/test_suites/java/org/apache/sysml/test/integration/functions/jmlc/ZPackageSuite.java b/src/test_suites/java/org/apache/sysml/test/integration/functions/jmlc/ZPackageSuite.java index 3d5c13a..05fa8bc 100644 --- a/src/test_suites/java/org/apache/sysml/test/integration/functions/jmlc/ZPackageSuite.java +++ b/src/test_suites/java/org/apache/sysml/test/integration/functions/jmlc/ZPackageSuite.java @@ -34,8 +34,10 @@ import org.junit.runners.Suite; FrameLeftIndexingTest.class, FrameReadMetaTest.class, FrameTransformTest.class, + JMLCClonedPreparedScriptTest.class, JMLCInputOutputTest.class, JMLCInputStreamReadTest.class, + JMLCParfor2ForCompileTest.class, ReuseModelVariablesTest.class, MulticlassSVMScoreTest.class })