Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
47 changes: 47 additions & 0 deletions alex_notes_on_profiling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Nodely Env Profiling Concerns

## Correctly measuring both sync and async eval

## Profiling demands a mutable place to put the profiled timings

## Profiling requires creating a single shot env

## We didn't scratch at any other interesting opportunities

# Classes of approach

## Shove everything in Metadata

Feels expedient, doesn't feel principled

## Change the implementation of engines so that they accomodate profiling

Feels expensive, most principled

## Sneak in a new implementation of the map data structure that lets us smuggle in timing

Hybrid feel, we're trying to change timing by changing the data structure we reduce in

# How to change the engine impls?

They already embody a bad lower-case p protocol.

Give them a Protocol, each engine implements the protocol between api.v0 and the engine.

Add another protocol that affords us the opportunity to capture eval start and end times for profiling (maybe).

Each engine implements the API protocol and the profiling protocol.

Can compose profiling choice and engine choice by having a no-op impl of the profiling protocol.

## key -> value installation per engine

lazy: assoc the k (node name) onto a derived version of env (derivation from node->value)

manifold: eager assoc futures to result env, futures are lazily evaled on deref, everything must be dereffed!

virtual_workers: eager assoc vfutures to result env, futures start running eagerly, everything must be dereffed!

lazy_scheduling: make a new map, access key lazy initializes k <promise-of v>. Get "evaled" map is get all scheduled keys and wait on them all materializing

applicative: make a new map, access key lazy initialized k <monadic context of expr>. Eval node is extract of the monadic context of that one key, eval full map is poke one key and then extract all monadic contexts lazy initialized
127 changes: 70 additions & 57 deletions src/nodely/api/v0.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[nodely.data]
[nodely.engine.applicative :as applicative]
[nodely.engine.core :as engine-core]
[nodely.engine.lazy]
[nodely.engine.lazy :as engine.lazy]
[nodely.engine.protocols :as engine.protocols]
[nodely.syntax :as syntax]
[nodely.vendor.potemkin :refer [import-fn import-vars]]))

Expand Down Expand Up @@ -72,38 +73,37 @@
:cause e}))))

(def engine-data
{:core-async.lazy-scheduling {::ns-name 'nodely.engine.core-async.lazy-scheduling
::opts-fn identity
::enable-deref core-async-failure
::eval-key-channel true}
:core-async.iterative-scheduling {::ns-name 'nodely.engine.core-async.iterative-scheduling
::opts-fn identity
::enable-deref core-async-failure}
:async.manifold {::ns-name 'nodely.engine.manifold
::opts-fn (constantly nil)
::enable-deref manifold-failure}
:applicative.promesa {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.promesa/context)))
::enable-deref promesa-failure}
:applicative.core-async {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.core-async/context)))
::eval-key-channel true
::enable-deref core-async-failure}
:sync.lazy {::ns-name 'nodely.engine.lazy
::opts-fn (constantly nil)
::eval-key-channel true
::enable-deref (delay nil)}
:async.virtual-futures {::ns-name 'nodely.engine.virtual-workers
::opts-fn (constantly nil)
::eval-key-channel true
::enable-deref virtual-future-failure}
:applicative.virtual-future {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.virtual-future/context)))
::eval-key-channel true
::enable-deref virtual-future-failure}})
{:core-async.lazy-scheduling {::ns-name 'nodely.engine.core-async.lazy-scheduling
::opts-fn identity
::enable-deref core-async-failure
::eval-key-channel true}
:core-async.iterative-scheduling {::ns-name 'nodely.engine.core-async.iterative-scheduling
::opts-fn identity
::enable-deref core-async-failure}
:async.manifold {::ns-name 'nodely.engine.manifold
::opts-fn (constantly nil)
::enable-deref manifold-failure}
:applicative.promesa {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.promesa/context)))
::enable-deref promesa-failure}
:applicative.core-async {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.core-async/context)))
::eval-key-channel true
::enable-deref core-async-failure}
:sync.lazy {::protocol-engine? true
::instance-constructor engine.lazy/->LazyEngine
::eval-key-channel true}
:async.virtual-futures {::ns-name 'nodely.engine.virtual-workers
::opts-fn (constantly nil)
::eval-key-channel true
::enable-deref virtual-future-failure}
:applicative.virtual-future {::ns-name 'nodely.engine.applicative
::opts-fn #(assoc % ::applicative/context
(var-get (resolve 'nodely.engine.applicative.virtual-future/context)))
::eval-key-channel true
::enable-deref virtual-future-failure}})

(defmacro >channel-leaf
[expr]
Expand All @@ -115,8 +115,7 @@
(mapv #'syntax/question-mark->keyword symbols-to-be-replaced)
fn-expr))))

(defn- engine-fn
[engine-name use]
(defn- data-engine-function [engine-name use]
(if-let [engine-data (engine-data engine-name)]
(if-let [{:keys [msg cause] :as enable-failure} @(::enable-deref engine-data)]
(throw (ex-info msg
Expand All @@ -129,41 +128,55 @@
{:specified-engine-name engine-name
:supported-engine-names (set (keys engine-data))}))))

(def engine-fn (memoize engine-fn))
(def engine-fn (memoize data-engine-function))

(defn eval
([env k]
(eval env k {}))
([env k {engine ::engine
:or {engine :core-async.lazy-scheduling}
:as opts}]

(let [efn (engine-fn engine 'eval)]
(if-let [opts ((::opts-fn (engine-data engine)) opts)]
(efn env k opts)
(efn env k)))))
([env k {engine-name ::engine
:or {engine-name :core-async.lazy-scheduling}
:as opts}]
(let [engine-data (engine-data engine-name)
protocol-engine? (::protocol-engine? engine-data)
engine (when protocol-engine? ((::instance-constructor engine-data)))]
(if protocol-engine?
(engine.protocols/eval engine env k (engine.protocols/-prepare-opts engine opts))
(let [efn (engine-fn engine-name 'eval)]
(if-let [opts ((::opts-fn engine-data) opts)]
(efn env k opts)
(efn env k)))))))

(defn eval-key
([env k]
(eval-key env k {}))
([env k {engine ::engine
:or {engine :core-async.lazy-scheduling}
:as opts}]
(let [efn (engine-fn engine 'eval-key)]
(if-let [opts ((::opts-fn (engine-data engine)) opts)]
(efn env k opts)
(efn env k)))))
([env k {engine-name ::engine
:or {engine-name :core-async.lazy-scheduling}
:as opts}]
(let [engine-data (engine-data engine-name)
protocol-engine? (::protocol-engine? engine-data)
engine (when protocol-engine? ((::instance-constructor engine-data)))]
(if protocol-engine?
(engine.protocols/eval-key engine env k (engine.protocols/-prepare-opts engine opts))
(let [efn (engine-fn engine-name 'eval-key)]
(if-let [opts ((::opts-fn engine-data) opts)]
(efn env k opts)
(efn env k)))))))

(defn eval-key-channel
([env k]
(eval-key-channel env k {}))
([env k {engine ::engine
:or {engine :core-async.lazy-scheduling}
([env k {engine-name ::engine
:or {engine-name :core-async.lazy-scheduling}
:as opts}]
(let [efn (engine-fn engine 'eval-key-channel)]
(if-let [opts ((::opts-fn (engine-data engine)) opts)]
(efn env k opts)
(efn env k)))))
(let [engine-data (engine-data engine-name)
protocol-engine? (::protocol-engine? engine-data)
engine (when protocol-engine? ((::instance-constructor engine-data)))]
(if protocol-engine?
(engine.protocols/eval-key-channel engine env k (engine.protocols/-prepare-opts engine opts))
(let [efn (engine-fn engine-name 'eval-key-channel)]
(if-let [opts ((::opts-fn engine-data) opts)]
(efn env k opts)
(efn env k)))))))

(defn eval-node
([env node]
Expand Down
32 changes: 32 additions & 0 deletions src/nodely/data.clj
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,38 @@
([node f]
(catch-node node f {})))

(comment
(require '[criterium.core :as criterium])

(defn time-body
[{:keys [k s]} f]
(fn [args]
(let [start# (criterium/timestamp)
ret# (f args)
finish# (criterium/timestamp)]
(swap! s assoc k (- finish# start#))
ret#)))

; (update-node (leaf #{} #(Thread/sleep 1000)) new-value {})

(do
(def my-atom (atom {}))
(def updated-node (env-update-helper (leaf #{} (fn [_] (Thread/sleep 1000))) {:k :x :s my-atom} {} time-body))

(def update-node-branch (let [condition (leaf #{} (fn [_] (do (Thread/sleep 1000) true)))
truthy (leaf #{} (fn [_] (Thread/sleep 1000)))
falsey (value 20)]
(env-update-helper (branch condition truthy falsey) {:k :y :s my-atom} {} time-body)))

{:x {:condition 1000 :truthy 1000 :falsey 1000}}

((:nodely.data/fn updated-node) {})
@my-atom
;
)
;
)

;;
;; Env Utils
;;
Expand Down
25 changes: 24 additions & 1 deletion src/nodely/engine/lazy.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,30 @@
(:require
[clojure.core.async :as async]
[nodely.data :as data]
[nodely.engine.core :as core]))
[nodely.engine.core :as core]
[nodely.engine.protocols :as engine.protocols]))

(defonce enable-deref (delay nil))

(deftype LazyEngine []
engine.protocols/Engine
(-eval [_engine env k _opts]
(core/resolve k env))

(-eval-key [engine env k opts]
(data/get-value (engine.protocols/-eval engine env k opts) k))

(-eval-key-channel [engine env k opts]
(async/thread (engine.protocols/-eval-key engine env k opts)))

(-eval-key-channel-supported? [_engine]
true)

(-enable-deref [_engine]
enable-deref)

(-prepare-opts [_engine _opts]
nil))

(defn eval
[env k]
Expand Down
27 changes: 27 additions & 0 deletions src/nodely/engine/protocols.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
(ns nodely.engine.protocols)

(defprotocol Engine
(-eval [engine env k opts] "Resolves node `k` in `env` using `engine`, returning the resulting env with `k` and its dependencies filled in with their evaluated values.")
(-eval-key [engine env k opts] "Resolves node `k` in `env` using `engine`, returning just the evaluated value of `k`.")
(-eval-key-channel [engine env k opts] "Resolves node `k` in `env` using `engine`, returning a core.async channel that yields the evaluated value of `k`.")
(-eval-key-channel-supported? [engine] "Returns true if `engine` supports `-eval-key-channel`, false otherwise.")
(-enable-deref [engine] "Returns a delay that yields nil if `engine` is available for use, or a map describing why it could not be enabled (e.g. a missing optional dependency) otherwise.")
(-prepare-opts [engine opts] "Transforms the caller-supplied `opts` map into the opts map expected by `engine`'s"))

(defn eval
([engine env k]
(-eval engine env k {}))
([engine env k opts]
(-eval engine env k opts)))

(defn eval-key
([engine env k]
(-eval-key engine env k {}))
([engine env k opts]
(-eval-key engine env k opts)))

(defn eval-key-channel
([engine env k]
(-eval-key-channel engine env k {}))
([engine env k opts]
(-eval-key-channel engine env k opts)))
Loading
Loading