[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
 })

Reply via email to