Fork me on GitHub
#graalvm-mobile
<
2021-07-16
>
dotemacs08:07:25

Just discovered this channel via borkdude’s tweet of the repo that is in the subject of the channel. Very cool! So since Asami https://github.com/threatgrid/asami has local storage capability, and can be graalvm compiled, any idea what would be needed for the following: - Asami graalvm compiled, but accessible from react-native ClojureScript app? I’m guessing that there would need to be some sort of binding so that Asami compiled could be interacted with from ClojureScript. Maybe somebody with a bit more graalvm experience can chime in on this? > Why would it be cool to have this? Just because we could have datalog database on mobile (with localstorage!). Now that I wrote the above, I guess the more succinct question is: what is needed to make graalvm compiled Clojure code accessible to react-native ClojureScript code? Thanks

raspasov09:07:02

I am currently using DataScript + React Native; All running in a JS thread, no “native”.

raspasov09:07:39

DataScript is alright, but there’s some gotchas around performance.

borkdude08:07:29

@dotemacs Another graalvm compatible datalog db is datalevin btw. So moar options :) But isn't Asami Clojure_Script_ compatible as well, could that be used from React Native + CLJS directly?

raspasov09:07:52

@dotemacs that’s an interesting idea

👍 2
dotemacs09:07:53

It could and it does work, but the local storage is not yet implemented on ClojureScript side. Only on Clojure side. This is a reply to @borkdude’s question above.

raspasov09:07:29

@borkdude what about DataScript? Has that been tried with GraalVM?

borkdude09:07:51

datalevin is based on datascript

borkdude09:07:05

datascript doesn't have durable storage, but datalevin added this

raspasov09:07:31

Right, I am using DataScript, hence my biased question… I rolled my naive DataScript durable storage.

borkdude09:07:45

it uses lmdb, you might have to have that lib available on your phone, not sure

borkdude09:07:58

@huahaiy might be able to give more insights here.

raspasov09:07:14

Since we brought up this interesting question and because of some perf issues in DataScript, I am theoretically curious if running it under GraalVM can yield any perf. improvements.

raspasov09:07:21

(I realize my question probably has no clear answer, it all needs to be benchmarked 🙂 )

4
dotemacs09:07:59

From what I understand, Datalevin doesn’t retain the history of transactions. So it’s slightly different to DataScript & Asami in that sense.

raspasov09:07:36

@dotemacs DataScript also does not retain history by design (you have to roll your own implementation)

raspasov09:07:43

No worries at all; I am no expert either; been using it for a few months;

dotemacs09:07:44

When you say > I rolled my naive DataScript durable storage Do you mean that you read the data into DataScript from a file and the write the data to a file upon the app going into background?

raspasov09:07:09

On every transaction save to file;

raspasov09:07:15

On app startup, read from file.

dotemacs09:07:37

And the overhead is what, in milliseconds?

raspasov09:07:41

Currently, it’s very naive; every time it simply dumps all datoms to a file.

raspasov09:07:04

So it would linearly slow down; but I’ve had 1000s of datoms and it hasn’t been an issue.

dotemacs09:07:19

Good to hear this

borkdude09:07:28

Perhaps Asami offers a better story here

borkdude09:07:33

if perf becomes an issue

raspasov09:07:34

It’s for data that the user generates on one device, so it would take years for it to grow super big.

dotemacs09:07:02

Yea, that makes sense

raspasov09:07:03

I don’t doubt it… I just wanted to stay as close to Datomic as possible, and DataScript more or less offers that.

👍 2
raspasov09:07:19

(not fully)

dotemacs09:07:26

Are you syncing with Datomic or …?

borkdude09:07:35

it's nice to have that affordance for sure on mobile

raspasov09:07:36

Not currently, potentially in the future…

raspasov09:07:01

The biggest issue with DataScript is querying perf.

raspasov09:07:26

Even with a few thousand datoms, complex queries can take 50-100ms (!!!) on iOS

raspasov09:07:46

So you have to be careful; I didn’t expect this initially and tripped me up. The DataScript home page oversells/oversimplifies the product, to put it mildly…

raspasov09:07:37

It was my own ignorance and lack of benchmarking. Other than that, I have had no issues so far.

dotemacs09:07:43

I did look at mentat aka datomish which was kind of a re-write of DataScript with a Sqlite backend: https://github.com/mozilla/mentat/tree/clojure (note the branch there clojure). And then a subsequent re-write in Rust… which seems to compile but haven’t got it running on device yet. Not that I tried very hard.

borkdude09:07:44

Perhaps a little bit of caching could help mitigate that

raspasov09:07:20

@borkdude Apparently it’s complicated; There’s a number of issues on github brining up performance; To do it well, you need to get into the weeds of “database science”; I’ve dabbled in general database design and it gets deep 🙂

raspasov09:07:21

Query planning, etc. The datalog queries are very expressive and it’s not trivial to optimize them; the fact that you’re doing everything in one thread (JS) doesn’t help either, I think.

borkdude09:07:32

I mean caching on the side of the app

borkdude09:07:35

not in DataScript

borkdude09:07:44

e.g. when you need to execute the same query over and over while the data hasn't changed

raspasov09:07:47

Ah, yes; That’s what I ended up doing;

raspasov09:07:46

It’s a bit tedious but it mostly works; Basically only re-query things that you know have changed based on the most recent transaction;

raspasov09:07:42

One of the “proper” ways to achieve better perf. would be some version of differential dataflow (not trivial to add, afaik) https://timelydataflow.github.io/differential-dataflow/

borkdude09:07:16

So are you using using datascript in native?

raspasov09:07:23

React Native

borkdude09:07:29

so it's CLJS

borkdude09:07:45

using a graalvm version might already speed up things considerably

raspasov09:07:54

You think? That’s probably worth a shot…

dotemacs09:07:39

I’ve seen that. CLJS is slower, as expected.

raspasov09:07:54

And then iOS, afact, is like 10x slower (no idea why)

dotemacs09:07:18

But how would you go about using Graalvm compiled db, from cljs?

raspasov09:07:45

I think the RN runtime of JavaScript core is a bit restricted as opposed to running in Safari… (just speculating as to the reasons)

borkdude09:07:05

@dotemacs mobiletest compiles the graalvm binary to a shared library. This shared library is then accessed from the mobile UI code. Not sure how to do this from React Native/CLJS. This probably relies on iOS specific APIs.

raspasov09:07:06

@borkdude So when you say “graalvm version”… Is that running in like a real native thread, or something like Sci? I apologize, still not very well versed in all the differences.

borkdude09:07:24

@raspasov what mobiletest does: it compiles everything to "real" native and then exposes hooks to SCI, so you can have dynamic behavior when you eval stuff in SCI.

borkdude09:07:54

SCI is an interpreter which you can use as glue code between your "real" compiled functions. Not everything is interpreted by SCI, only the glue code, let's say.

raspasov09:07:19

Got it… so some Clojure is compiled to “real” native.

borkdude09:07:50

so if you have {:namespaces {'foobar {'foobar foobar}}} and then eval (require 'foobar) (foobar/foobar) in SCI, then the foobar call is exactly the same as outside SCI

raspasov09:07:50

Some is interpreted, to help provide REPL-like functionality?

raspasov09:07:35

I have to go through the mobiletest example again with the latest updates; thank you 🙂

raspasov09:07:41

When you compile DataScript/etc database to “real native”, I assume you’d have to communicate with it via async function calls with the React Native JS thread.

borkdude09:07:14

sure, but the shared library can be sync. The async stuff is a JS problem

borkdude09:07:32

similar to how a REST endpoint is usually synchronous

raspasov09:07:01

Right… when you say “shared library” that’s basically a regular method/fn call for other native code. Basically a “real” native library?

Huahai17:07:41

I don’t think compile Datascript with GraalVM will help much with performance

Huahai17:07:12

The poor query performance of Datascript is mostly due to its lack of query optimization. I did some trivial optimization in Datalevin and the performance increases about 30% to 50% depending on dataset. I also have a plan to do a rewrite of the query engine in Datalevin. In any case, the slow performance of triple-store (compared with relational DB) is a well known problem, but that should not excuse Datomic and Datascript from doing any optimization at all. Currently, Asami does a decent job in query optimization, Crux claims to do that as well, but the effect may not be obvious from what I heard. Of course, this is just my opinion and I am obviously biased.

👍 5
phronmophobic16:07:44

@dotemacs > what is needed to make graalvm compiled Clojure code accessible to react-native ClojureScript code? TLDR: You would need to make a native module, https://reactnative.dev/docs/native-modules-ios Roughly, the steps required would be: 1. Compile your clojure code to a shared library with native-image You need to explicitly mark functions that are available as part of the shared library API. Further, the API can only receive and return C data structures like ints, chars, pointers, structs, etc. 2. Create a native module that exposes the shared library API to your js code. Not sure how it would affect performance or memory usage, but I assume there is some overhead in the bridge between JS and native code. I think an interesting alternative would be to compile all your clojure code to native and talk to react native code directly. I've been looking at some of the UI options for mobile and I think one of the major complaints against react native is that the JS/native bridge is slow.

👍 2
dotemacs16:07:32

I figured that those would be the steps @smith.adriane but what I’m unsure of is this part: > You need to explicitly mark functions that are available as part of the shared library API How would something like that be done for Clojure functions that would then be compiled via GraalVM?

phronmophobic16:07:15

It looks like:

(defn compile-interface-class
  ([]
   (compile-interface-class nil))
  ([opts]
   (with-bindings {#'*compile-path* "library/classes"}
     ((requiring-resolve 'tech.v3.datatype.ffi.graalvm/expose-clojure-functions)
      {
       #'clj_print {:rettype :void
                    :argtypes [['bs :pointer]]}

       #'clj_prn {:rettype :void
                  :argtypes [['bs :int64]]}

       #'clj_eval {:rettype :int64
                   :argtypes [['bs :pointer]]}
       #'clj_start_server {:rettype :void
                           :argtypes []}

       #'clj_print_hi {:rettype :void
                       :argtypes []}
       }
      'com.phronemophobic.mobiletest.interface nil)))
  )
where clj_print, clj_prn, etc are just normal clojure functions that receive and return the arg types listed

dotemacs16:07:18

Oh, I’m with you, that’s from your project. Sorry for being slow on the uptake 🙂

simple_smile 2