Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #381 Reduce the number of Eval calls #396

Merged
merged 15 commits into from
Aug 13, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 38 additions & 32 deletions src/main/clojure/clara/rules/compiler.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,12 @@
:join-filter-expressions (:join-filter-expressions beta-node)
:env (:env beta-node)
:msg "compiling accumulate with join filter node"}}))
:query prev)))
:query prev

;; This error should only be thrown if there are changes to the compilation of the beta-graph
;; such as an addition of a node type.
(throw (ex-info "Invalid node type encountered while compiling rulebase."
{:node beta-node})))))
id->expr
(:id-to-condition-node beta-graph))))

Expand Down Expand Up @@ -1448,9 +1453,10 @@
"A helper function for retrieving a given key from the provided map. If the key doesn't exist within the map this
function will throw an exception."
[m k]
(let [v (get m k ::not-found)]
(if (= v ::not-found)
(throw (ex-info "Key not found with safe-get" {:map map :key k}))
(let [not-found ::not-found
v (get m k not-found)]
(if (identical? v not-found)
(throw (ex-info "Key not found with safe-get" {:map m :key k}))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this approach to enforcing identity equality

v)))

(sc/defn ^:private compile-node
Expand Down Expand Up @@ -1875,34 +1881,34 @@
(throw (ex-info (str "Non-unique production names: " non-unique) {:names non-unique})))))

(def forms-per-eval-default
;; The default max number of forms that will be evaluated together as a single batch.
;; 5000 is chosen here due to the way that clojure will evaluate the vector of forms extracted from the nodes.
;; The limiting factor here is the max java method size (64KiB), clojure will compile each form in the vector down into
;; its own class file and generate another class file that will reference each of the other functions and wrap them in
;; a vector inside a static method. For example,
;;
;; (eval [(fn one [_] ...) (fn two [_] ...)])
;; would generate 3 classes.
;;
;; some_namespace$eval1234
;; some_namespace$eval1234$one_1234
;; some_namespace$eval1234$two_1235
;;
;; some_namespace$eval1234$one_1234 and some_namespace$eval1234$two_1235 contian the implementation of the functions,
;; where some_namespace$eval1234 will contain two methods, invoke and invokeStatic.
;; The invokeStatic method breaks down into something similar to a single create array call followed by 2 array set calls
;; with new invocations on the 2 classes the method then returns a new vector created from the array.
;;
;; 5000 is lower than the absolute max to allow for modifications to how clojure compiles without needing to modify this.
;; The current limit should be 5471, this is derived from the following opcode investigation:
;;
;; Array creation: 5B
;; Creating and populating the first 6 elements of the array: 60B
;; Creating and populating the next 122 elements of the array: 1,342B
;; Creating and populating the next 5343 elements of the array: 64,116B
;; Creating the vector and the return statement: 4B
;;
;; This sums to 65,527B just shy of the 65,536B method size limit.
"The default max number of forms that will be evaluated together as a single batch.
5000 is chosen here due to the way that clojure will evaluate the vector of forms extracted from the nodes.
The limiting factor here is the max java method size (64KiB), clojure will compile each form in the vector down into
its own class file and generate another class file that will reference each of the other functions and wrap them in
a vector inside a static method. For example,

(eval [(fn one [_] ...) (fn two [_] ...)])
would generate 3 classes.

some_namespace$eval1234
some_namespace$eval1234$one_1234
some_namespace$eval1234$two_1235

some_namespace$eval1234$one_1234 and some_namespace$eval1234$two_1235 contian the implementation of the functions,
where some_namespace$eval1234 will contain two methods, invoke and invokeStatic.
The invokeStatic method breaks down into something similar to a single create array call followed by 2 array set calls
with new invocations on the 2 classes the method then returns a new vector created from the array.

5000 is lower than the absolute max to allow for modifications to how clojure compiles without needing to modify this.
The current limit should be 5471, this is derived from the following opcode investigation:

Array creation: 5B
Creating and populating the first 6 elements of the array: 60B
Creating and populating the next 122 elements of the array: 1,342B
Creating and populating the next 5343 elements of the array: 64,116B
Creating the vector and the return statement: 4B

This sums to 65,527B just shy of the 65,536B method size limit."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but this increases the importance of setting up our tests against all relevant Clojure versions in Travis since potentially different/future Clojure versions could have differences here. That is really something we should be doing anyway though.

5000)

(sc/defn mk-session*
Expand Down
13 changes: 9 additions & 4 deletions src/main/clojure/clara/rules/durability/fressian.clj
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@
d/ISessionSerializer
(serialize [_ session opts]
(let [{:keys [rulebase memory]} (eng/components session)
node-fns (:node-expr-fn-lookup rulebase)
node-expr-fn-lookup (:node-expr-fn-lookup rulebase)
remove-node-fns (fn [expr-lookup]
(zipmap (keys expr-lookup)
(mapv second (vals expr-lookup))))
Expand All @@ -553,13 +553,18 @@

;; In this case there is nothing to do with memory, so just serialize immediately.
(if (:rulebase-only? opts)
;; The keys of the node-fns should contain enough data to reconstruct the entire map.
(do-serialize [(remove-node-fns node-fns) rulebase])
;; node-expr-fn-lookup is a map with a structure of:
;; {[Int Keyword] [IFn {Keyword Any}]}
;; as fns are not serializable, we must remove them and alter the structure of the map to be
;; {[Int Keyword] {Keyword Any}}
;; during deserialization the compilation-context({Keyword Any}), which contains the unevaluated form,
;; can be used to reconstruct the original map.
(do-serialize [(remove-node-fns node-expr-fn-lookup) rulebase])

;; Otherwise memory needs to have facts extracted to return.
(let [{:keys [memory indexed-facts internal-indexed-facts]} (d/indexed-session-memory-state memory)
sources (if (:with-rulebase? opts)
[(remove-node-fns node-fns) rulebase internal-indexed-facts memory]
[(remove-node-fns node-expr-fn-lookup) rulebase internal-indexed-facts memory]
[internal-indexed-facts memory])]

(do-serialize sources)
Expand Down