packages feed

jvm-batching 0.1.2 → 0.2.0

raw patch · 6 files changed

+238/−64 lines, 6 filesdep ~inline-javadep ~jnidep ~jvm

Dependency ranges changed: inline-java, jni, jvm

Files

README.md view
@@ -2,6 +2,22 @@  Provides batched marshalling of values between Java and Haskell. +It provides reify and reflect instances for vectors, which marshal+values in batches, which is more efficient than marshalling values+one at a time.++```Haskell+instance (Interpretation a, BatchReify a) => Reify (V.Vector a) where+  ...++instance (Interpretation a, BatchReflect a) => Reflect (V.Vector a) where+  ...+```++See the documentation in+[Language.Hava.Batching](src/main/haskell/Language/Java/Batching.hs)+for an overview on how the implementation works.+ ## Using it as a dependency  Add `jvm-batching` to the list of dependencies in your .cabal file.
jvm-batching.cabal view
@@ -1,5 +1,5 @@ name:                jvm-batching-version:             0.1.2+version:             0.2.0 synopsis:            Provides batched marshalling of values between Java and Haskell. description:         Please see README.md. homepage:            http://github.com/tweag/inline-java/tree/master/jvm-batching#readme@@ -43,9 +43,9 @@     base >= 4.7 && < 5,     bytestring,     distributed-closure >= 0.3.5,-    jni >= 0.7.0 && <0.8,-    jvm >= 0.5.0 && <0.6,-    inline-java >= 0.7 && <0.10,+    jni >= 0.8.0 && <0.9,+    jvm >= 0.6.0 && <0.7,+    inline-java >= 0.10 && <0.11,     singletons >= 2.2,     text,     vector@@ -63,11 +63,13 @@     bytestring,     hspec,     inline-java,+    jni,     jvm,     jvm-batching,     text,     vector   default-language: Haskell2010+  ghc-options: -threaded   cpp-options: -DHSPEC_DISCOVER=hspec-discover  benchmark micro-benchmarks
src/main/haskell/Language/Java/Batching.hs view
@@ -21,6 +21,16 @@  -- | This module provides composable batched marshalling. --+-- It provides reify and reflect instances for vectors, which marshal+-- values in batches, which is more efficient than marshalling values+-- one at a time.+--+-- > instance (Interpretation a, BatchReify a) => Reify (V.Vector a) where+-- > ...+--+-- > instance (Interpretation a, BatchReflect a) => Reflect (V.Vector a) where+-- > ...+-- -- === Batching -- -- Calls to Java methods via JNI are slow in general. Marshalling an array of@@ -109,6 +119,33 @@ -- the 'ByteString's in the batch. The other array contains the offset of each -- vector in the resulting array. See 'ArrayBatch'. --+-- === null values in batches+--+-- Suppose we have an array of pairs in Java:+--+-- > new Tuple2<Integer,Integer>[] { null, new Tuple2(1, 2), new Tuple2(3, 4) }+--+-- In Haskell we can represent it using the 'Nullable' type+--+-- > [ Null, NotNull (1, 2), NotNull (3, 4) ]+--+-- However, we can't use the batch type we defined above for @Tuple2@,+-- because there is no way to store a null value in a pair of primitive arrays.+--+-- For this reason we define a special batch type for values that can be null.+-- We place a boolean array together with the regular batch of values.+--+-- > type Batch (Nullable a) =+-- >   'Class "io.tweag.jvm.batching.Tuple2" <>+-- >     '[ 'Array ('Prim "boolean")+-- >      , ArrayBatch (Batch a)+-- >      ]+--+-- The positions in the batch for type @a@ are only meaningful if the boolean+-- array holds @True@ in the corresponding positions, the remaining positions+-- are regarded to hold null values.+--+ module Language.Java.Batching   ( Batchable(..)   , BatchReify(..)@@ -175,27 +212,42 @@   newBatchWriter _ = generic <$> [java| new BatchWriters.ObjectBatchWriter() |]    -- | Reifies the values in a batch of type @Batch a@.-  -- Gets the batch and the amount of elements it contains.-  reifyBatch :: J (Batch a) -> Int32 -> IO (V.Vector a)+  --+  -- Gets the batch, the amount of elements it contains and a predicate that+  -- indicates which positions of the batch to reify. The value at position i+  -- in the returned vector holds bottom if @p i@ is False.+  --+  -- The predicate is used by instances of BatchReify to control which+  -- positions are to be reified in the batches of the components of a+  -- composite type whose values are allowed to be null.+  reifyBatch :: J (Batch a) -> Int32 -> (Int -> Bool) -> IO (V.Vector a)    -- The default implementation makes calls to the JVM for each element in the   -- batch.   default reifyBatch     :: (Reify a, Batch a ~ 'Array (Interp a))-    => J (Batch a) -> Int32 -> IO (V.Vector a)-  reifyBatch jxs size =-      V.generateM (fromIntegral size) $ \i ->-      getObjectArrayElement jxs (fromIntegral i) >>= reify . unsafeCast+    => J (Batch a) -> Int32 -> (Int -> Bool) -> IO (V.Vector a)+  reifyBatch jxs size p =+    V.generateM (fromIntegral size) $ \i ->+      if p i then+        getObjectArrayElement jxs (fromIntegral i) >>= reify . unsafeCast+      else+        return $ error "default reifyBatch: reification skipped."  -- | Helper for reifying batches of primitive types reifyPrimitiveBatch   :: Storable a   => (J ('Array ty) -> IO (Ptr a))   -> (J ('Array ty) -> Ptr a -> IO ())-  -> J ('Array ty) -> Int32 -> IO (V.Vector a)-reifyPrimitiveBatch getArrayElements releaseArrayElements jxs size = do-    bracket (getArrayElements jxs) (releaseArrayElements jxs)-      $ V.generateM (fromIntegral size) . peekElemOff+  -> J ('Array ty)+  -> Int32+  -> (Int -> Bool)+  -> IO (V.Vector a)+reifyPrimitiveBatch getArrayElements releaseArrayElements jxs size p = do+    bracket (getArrayElements jxs) (releaseArrayElements jxs) $ \arr ->+      V.generateM (fromIntegral size) $ \i ->+        if p i then peekElemOff arr i+        else return $ error "default reifyBatch: reification skipped."  -- | Batches of arrays of variable length --@@ -227,8 +279,9 @@   -> (Int -> Int -> a -> IO b) -- ^ slice at a given offset of given length of some array a   -> J (ArrayBatch ty)   -> Int32+  -> (Int -> Bool)   -> IO (V.Vector b)-reifyArrayBatch reifyB slice batch0 batchSize = do+reifyArrayBatch reifyB slice batch0 batchSize p = do     let batch = unsafeUngeneric batch0     arrayEnds <- reifyArrayOffsets batch     arrayValues <- reifyArrayValues arrayEnds batch@@ -261,10 +314,18 @@       -> Int             -- ^ index of the position to write in the output vector       -> IO Int32        -- ^ offset of the next slice to read     writeSliceToVector output arrayEnds arrayValues offset i = do-        slice (fromIntegral offset)-              (fromIntegral $ arrayEnds VS.! i - offset) arrayValues-          >>= VM.unsafeWrite output i-        return $ arrayEnds VS.! i+        -- Account for the posibility of @arrayEnds ! i@ to be 0+        -- in case the position in the batch has been left uninitialized.+        let nextOffset = max (arrayEnds VS.! i) offset+        if p i then do+          slice (fromIntegral offset)+                (fromIntegral $ nextOffset - offset) arrayValues+            >>= VM.unsafeWrite output i+          return nextOffset+        else do+          VM.unsafeWrite output i $+            error "reifyPrimitiveArrayBatch: reification skipped."+          return offset  -- | A class for batching reflection of values. --@@ -354,12 +415,15 @@    instance BatchReify Bool where     newBatchWriter _ = [java| new BatchWriters.BooleanBatchWriter() |]-    reifyBatch jxs size = do+    reifyBatch jxs size p = do         let toBool w = if w == 0 then False else True         bracket (getBooleanArrayElements jxs)                 (releaseBooleanArrayElements jxs) $-          \arr -> V.generateM (fromIntegral size)-                              ((toBool <$>) . peekElemOff arr)+          \arr -> V.generateM (fromIntegral size) $ \i ->+            if p i then+              toBool <$> peekElemOff arr i+            else+              return $ error "reifyBatch Bool: reification skipped."    instance Batchable CChar where     type Batch CChar = 'Array ('Prim "byte")@@ -596,52 +660,89 @@    instance (Interpretation a, BatchReify a)            => Reify (V.Vector a) where-    reify jv = do-        _batcher <- unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a)-        n <- getArrayLength jv-        let _jvo = arrayUpcast jv-        batch <- [java| {-          $_batcher.start($n);-          for(int i=0;i<$_jvo.length;i++)-             $_batcher.set(i, $_jvo[i]);-          return $_batcher.getBatch();-          } |]-        reifyBatch (fromObject batch) n+    reify jv =+        withLocalRef (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))+        $ \_batcher -> do+          n <- getArrayLength jv+          let _jvo = arrayUpcast jv+          withLocalRef [java| {+            $_batcher.start($n);+            for(int i=0;i<$_jvo.length;i++)+               $_batcher.set(i, $_jvo[i]);+            return $_batcher.getBatch();+            } |]+            $ \batch ->+              reifyBatch (fromObject batch) n (const True)       where         fromObject :: JObject -> J x         fromObject = unsafeCast    instance (Interpretation a, BatchReflect a)            => Reflect (V.Vector a) where-    reflect v = do-        _batch <- upcast <$> reflectBatch v-        _batchReader <--          unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a)-        jv <- [java| {-          $_batchReader.setBatch($_batch);-          return $_batchReader.getSize();-          } |] >>= newArray :: IO (J ('Array (Interp a)))-        let _jvo = arrayUpcast jv-        () <- [java| {-          for(int i=0;i<$_jvo.length;i++)-            $_jvo[i] = $_batchReader.get(i);-          } |]-        return jv+    reflect v =+        withLocalRef (upcast <$> reflectBatch v) $ \_batch ->+        withLocalRef (unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a))+        $ \_batchReader -> do+          jv <- [java| {+            $_batchReader.setBatch($_batch);+            return $_batchReader.getSize();+            } |] >>= newArray :: IO (J ('Array (Interp a)))+          let _jvo = arrayUpcast jv+          () <- [java| {+            for(int i=0;i<$_jvo.length;i++)+              $_jvo[i] = $_batchReader.get(i);+            } |]+          return jv    instance Batchable a => Batchable (V.Vector a) where     type Batch (V.Vector a) = ArrayBatch (Batch a)    instance BatchReify a => BatchReify (V.Vector a) where-    newBatchWriter _ = do-        _b <- unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a)-        generic <$> [java| new BatchWriters.ObjectArrayBatchWriter($_b) |]+    newBatchWriter _ =+        withLocalRef+          (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))+          $ \_b ->+            generic <$> [java| new BatchWriters.ObjectArrayBatchWriter($_b) |]     reifyBatch =-        reifyArrayBatch (flip reifyBatch) (fmap (fmap return) . V.unsafeSlice)+        reifyArrayBatch+          -- skipped values do not leave holes in a batch of arrays, so it+          -- is safe to reify all of the values+          (\sz j -> reifyBatch j sz (const True))+          (fmap (fmap return) . V.unsafeSlice)    instance BatchReflect a => BatchReflect (V.Vector a) where-    newBatchReader _ = do-        _b <- unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a)-        generic <$> [java| new BatchReaders.ObjectArrayBatchReader($_b) |]+    newBatchReader _ =+        withLocalRef+          (unsafeUngeneric <$> newBatchReader (Proxy :: Proxy a))+          $ \_b ->+            generic <$> [java| new BatchReaders.ObjectArrayBatchReader($_b) |]     reflectBatch =         reflectArrayBatch reflectBatch V.length (return . V.concat)++  instance Batchable a => Batchable (Nullable a) where+    type Batch (Nullable a) =+      'Class "io.tweag.jvm.batching.Tuple2" <>+        '[ 'Array ('Prim "boolean")+         , ArrayBatch (Batch a)+         ]++  instance BatchReify a => BatchReify (Nullable a) where+    newBatchWriter _ =+      withLocalRef+        (unsafeUngeneric <$> newBatchWriter (Proxy :: Proxy a))+        $ \_baseBatcher ->+          generic <$> [java| new BatchWriters.NullableBatchWriter($_baseBatcher) |]++    reifyBatch jxs n p = do+      let _batch = unsafeUngeneric jxs+      isnull <- withLocalRef [java| $_batch._1 |] $ \j ->+        V.convert <$> (reify (unsafeCast (j :: JObject)) :: IO (VS.Vector W8Bool))+      let p' i = p i && isnull V.! i == 0+      b <- withLocalRef [java| $_batch._2 |] $ \j0 ->+         (\j -> reifyBatch j n p') (unsafeCast (j0 :: JObject) :: J (Batch a))+      return $ V.zipWith toNullable isnull b+      where+        toNullable :: W8Bool -> a -> Nullable a+        toNullable 0 a = NotNull a+        toNullable _ _ = Null  |]
src/main/java/io/tweag/jvm/batching/BatchWriters.java view
@@ -341,6 +341,8 @@         private int[] end;         // top tells the first position not occupied by arrays in the batch.         private int top;+        // index of the next position available in the batch+        private int nextIndex;          public ObjectArrayBatchWriter(BatchWriter<T, B> ob) {             this.ob = ob;@@ -350,6 +352,7 @@             end = new int[size];             arrays = (T[][]) new Object[size][];             top = 0;+            nextIndex = 0;         }         public void set(int i, T[] vec) {             if (vec == null)@@ -357,18 +360,21 @@             arrays[i] = vec;             top += vec.length;             end[i] = top;+            nextIndex = i + 1;         }         public Tuple2<B, int[]> getBatch() {             ob.start(top);             int k = 0;-            for(int i=0;i<end.length && arrays[i]!=null;i++) {-                for(int j=0;j<arrays[i].length;j++) {-                    ob.set(k, arrays[i][j]);-                    k++;+            for(int i=0;i<nextIndex;i++) {+                if (arrays[i] != null) {+                    for(int j=0;j<arrays[i].length;j++) {+                        ob.set(k, arrays[i][j]);+                        k++;+                    }+                    // Release the reference to the array so it can be reclaimed+                    // before the loop is over.+                    arrays[i] = null;                 }-                // Release the reference to the array so it can be reclaimed-                // before the loop is over.-                arrays[i] = null;             }             return new Tuple2<B, int[]>(ob.getBatch(), end);         }@@ -404,6 +410,28 @@                 arrays[i] = null;             }             return new Tuple2<char[], int[]>(batch, end);+        }+    }++    public static final class NullableBatchWriter<A, B>+            implements BatchWriter<A, Tuple2<boolean[], B>> {+        private boolean isnull[];+        final BatchWriter<A, B> b;+        public NullableBatchWriter(BatchWriter<A, B> b) {+            this.b = b;+        }+        public void start(int size) {+            b.start(size);+            isnull = new boolean[size];+        }+        public void set(int i, A a) {+            if (null == a)+                isnull[i] = true;+            else+                b.set(i, a);+        }+        public Tuple2<boolean[], B> getBatch() {+            return new Tuple2(isnull, b.getBatch());         }     } }
src/test/haskell/Language/Java/BatchingSpec.hs view
@@ -12,6 +12,7 @@ import qualified Data.Vector.Storable as VS import Language.Java import Language.Java.Batching ()+import Language.Java.Inline import Test.Hspec  spec :: Spec@@ -23,6 +24,24 @@         testrr (V.fromList [] :: V.Vector Text)       it "succeeds on non-empty vectors" $ do         testrr (V.fromList [1..10] :: V.Vector Int32)+      it "succeeds on vectors with nulls" $ do+        v <- [java| new Integer[] {1, null, 2, null } |]+        reify v `shouldReturn` (V.fromList [NotNull 1, Null, NotNull 2, Null] :: V.Vector (Nullable Int32))+      it "succeeds on vectors of vectors with nulls" $ do+        v <- [java| new Integer[][] {+            new Integer[] {},+            null,+            new Integer[] { 2, 3 },+            null,+            new Integer[] {}+          } |]+        reify v `shouldReturn` ((V.fromList+          [ NotNull V.empty+          , Null+          , NotNull (V.fromList [2, 3])+          , Null+          , NotNull V.empty+          ]) :: V.Vector (Nullable (V.Vector Int32)))       it "succeeds on vectors of vectors" $ do         let xs :: V.Vector (V.Vector Int32)             xs = V.fromList (map V.fromList [ [i .. i + 4] | i <- [1,6..26] ])
src/test/haskell/Main.hs view
@@ -1,10 +1,18 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} module Main where -import Language.Java (withJVM)+import Control.Concurrent (runInBoundThread)+import Foreign.JNI+import Language.Java.Inline (loadJavaWrappers) import qualified Spec import Test.Hspec  main :: IO ()-main = withJVM ["-Djava.class.path=build/libs/jvm-batching.jar"] $-    hspec Spec.spec+main = withJVM ["-Djava.class.path=build/libs/jvm-batching.jar"] $ do+    loadJavaWrappers -- causes classes in jvm-batching.jar to be loaded+                     -- since they are used in java quotations+    hspec $ around_ (runInBoundThread . runInAttachedThread) Spec.spec