-
Notifications
You must be signed in to change notification settings - Fork 3
Parallel evaluator #48
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
Changes from all commits
8fdd673
2b10ea7
c575705
b0f5c55
d34a593
cdf4249
ea399b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| a = 1; | ||
| b = 2; | ||
| c = 3; | ||
| d = 4; | ||
| e = 5; | ||
| f = 6; | ||
| g = 7; | ||
| h = 8; | ||
| i = 9; | ||
| j = 10; | ||
| k = 11; | ||
| l = 12; | ||
| m = 13; | ||
| n = 14; | ||
| o = 15; | ||
| p = 16; | ||
|
|
||
| add = create_blob("./target/x86_64-unknown-none/addblob"); | ||
|
|
||
| ab = create_application_thunk(create_tree(add, a, b)); | ||
| cd = create_application_thunk(create_tree(add, c, d)); | ||
| ef = create_application_thunk(create_tree(add, e, f)); | ||
| gh = create_application_thunk(create_tree(add, g, h)); | ||
| ij = create_application_thunk(create_tree(add, i, j)); | ||
| kl = create_application_thunk(create_tree(add, k, l)); | ||
| mn = create_application_thunk(create_tree(add, m, n)); | ||
| op = create_application_thunk(create_tree(add, o, p)); | ||
|
|
||
| sum_ab = create_strict_encode(ab); | ||
| sum_cd = create_strict_encode(cd); | ||
| sum_ef = create_strict_encode(ef); | ||
| sum_gh = create_strict_encode(gh); | ||
| sum_ij = create_strict_encode(ij); | ||
| sum_kl = create_strict_encode(kl); | ||
| sum_mn = create_strict_encode(mn); | ||
| sum_op = create_strict_encode(op); | ||
|
|
||
| lefti = create_application_thunk(create_tree(add, sum_ab, sum_cd)); | ||
| righti = create_application_thunk(create_tree(add, sum_ef, sum_gh)); | ||
| leftr= create_application_thunk(create_tree(add, sum_ij, sum_kl)); | ||
| rightr = create_application_thunk(create_tree(add, sum_mn, sum_op)); | ||
|
|
||
| sum_lefti = create_strict_encode(lefti); | ||
| sum_righti = create_strict_encode(righti); | ||
| sum_leftr = create_strict_encode(leftr); | ||
| sum_rightr = create_strict_encode(rightr); | ||
|
|
||
| left = create_application_thunk(create_tree(add, sum_lefti, sum_leftr)); | ||
| right = create_application_thunk(create_tree(add, sum_righti, sum_rightr)); | ||
|
|
||
| sum_left = create_strict_encode(left); | ||
| sum_right = create_strict_encode(right); | ||
|
|
||
| final = create_application_thunk(create_tree(add, sum_left, sum_right)); | ||
|
|
||
| eval(create_strict_encode(final)); | ||
|
|
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we necessarily need a separate serial and parallel evaluator? If we have them we should probably try to deduplicate the code between them, but I think we'll eventually only want the parallel one so we might we well just replace the serial one.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is my fault -- I had thought that (especially when we move the evaluators to user-space) we probably want to keep a simple single-threaded evaluator around so we can run it for simplicity and debugging and have for teaching/explanatory purposes...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now, I’ll keep the serial and parallel evaluators separate, but let me know if you’d prefer that I merge them instead. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| extern crate alloc; | ||
| use crate::scheduler::{Scheduler, Task}; | ||
| use alloc::sync::Arc; | ||
| use kernel::{coreid, kthread}; | ||
|
|
||
| use crate::handle::*; | ||
| use crate::runtime::Runtime; | ||
| use crate::storage::Storage; | ||
| use kernel::prelude::*; | ||
|
|
||
| const NUM_WORKERS: usize = 19; | ||
| #[derive(Clone, Copy)] | ||
| enum EvalType { | ||
| Parallel, | ||
| Serial, | ||
| } | ||
| pub struct Evaluator<R: Runtime> { | ||
| runtime: R, | ||
| scheduler: Scheduler, | ||
| } | ||
|
|
||
| impl<R: Runtime> Evaluator<R> { | ||
| pub fn new(runtime: R) -> Arc<Self> { | ||
| let evaluator = Arc::new(Self { | ||
| runtime, | ||
| scheduler: Scheduler::new(), | ||
| }); | ||
|
|
||
| evaluator.start_workers(NUM_WORKERS); | ||
| evaluator | ||
| } | ||
|
|
||
| pub fn start_workers(self: &Arc<Self>, num_workers: usize) { | ||
| for i in 0..num_workers { | ||
| let evaluator = Arc::clone(self); | ||
| kthread::spawn(move || { | ||
| println!("worker {} started on core {}", i, coreid()); | ||
| evaluator.worker_loop(i); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| pub fn worker_loop(self: &Arc<Self>, _worker_id: usize) { | ||
| loop { | ||
| // eventually make this a queue to handle local queue of work | ||
| if let Some(work) = self.scheduler.get_work() { | ||
| //println!("worker {} evaluating task on core {}", worker_id, coreid()); | ||
|
|
||
| let result = self.eval_test(work.get_handle(), EvalType::Parallel); | ||
| work.task_complete(result) | ||
| } else { | ||
| //change this to condition variable, so it does no busy waiting? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will most likely have to implement a condition variable using atomics, which I think should be a separate follow-up PR. |
||
| kthread::yield_now() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn wait_while_helping(&self, target: &Arc<Task>) -> Handle { | ||
| loop { | ||
| // The task we are waiting for has completed. | ||
| if target.is_complete() { | ||
| return target.take_result(); | ||
| } | ||
|
|
||
| if let Some(work) = self.scheduler.get_work() { | ||
| //println!("calling thread helping on core {}", coreid()); | ||
|
|
||
| let result = self.eval_test(work.get_handle(), EvalType::Parallel); | ||
|
|
||
| work.task_complete(result); | ||
| } else { | ||
| // Workers already claimed all available tasks. | ||
| kthread::yield_now(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn storage(&self) -> &dyn Storage { | ||
| self.runtime.storage() | ||
| } | ||
|
|
||
| fn apply(&self, combination: Tree) -> Handle { | ||
| self.runtime.execute(combination) | ||
| } | ||
|
|
||
| pub fn lift(&self, handle: Handle) -> Handle { | ||
| match handle { | ||
| Handle::Ref(r) => match r { | ||
| Ref::Tree(t) => Object::Tree(t).into(), | ||
| Ref::Blob(b) => Object::Blob(b).into(), | ||
| }, | ||
| _ => handle, | ||
| } | ||
| } | ||
|
|
||
| pub fn lower(&self, handle: Handle) -> Handle { | ||
| match handle { | ||
| Handle::Object(r) => match r { | ||
| Object::Tree(t) => Ref::Tree(t).into(), | ||
| Object::Blob(b) => Ref::Blob(b).into(), | ||
| }, | ||
| _ => handle, | ||
| } | ||
| } | ||
|
|
||
| fn think(&self, thunk: Thunk, eval_mode: EvalType) -> Handle { | ||
| match thunk { | ||
| Thunk::Identification(reference) => self.lift(Handle::Ref(reference)), | ||
| Thunk::Selection(_) => todo!(), | ||
| Thunk::Application(tree) => { | ||
| let evaled = self.eval_tree(tree, eval_mode); | ||
| self.apply(evaled) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn force(&self, thunk: Thunk, eval_mode: EvalType) -> Handle { | ||
| let thought = self.think(thunk, eval_mode); | ||
| match thought { | ||
| Handle::Object(_) => thought, | ||
| Handle::Ref(_) => self.lift(thought), | ||
| Handle::Thunk(_) | Handle::Encode(_) => todo!(), | ||
| } | ||
| } | ||
|
|
||
| fn encode(&self, encode: Encode, eval_mode: EvalType) -> Handle { | ||
| match encode { | ||
| Encode::Strict(thunk) => self.lift(self.force(thunk, eval_mode)), | ||
| Encode::Shallow(thunk) => self.lower(self.force(thunk, eval_mode)), | ||
| } | ||
| } | ||
|
|
||
| fn eval_tree(&self, handle: Tree, eval_mode: EvalType) -> Tree { | ||
| match eval_mode { | ||
| EvalType::Serial => self.eval_tree_seq(handle), | ||
| EvalType::Parallel => self.eval_tree_parallel(handle), | ||
| } | ||
| } | ||
|
|
||
| fn eval_tree_seq(&self, handle: Tree) -> Tree { | ||
| let tree = self.runtime.storage().get_tree(handle).unwrap(); | ||
| let evaled: Vec<Handle> = tree | ||
| .as_ref() | ||
| .iter() | ||
| .copied() | ||
| .map(|x| self.eval_test(x, EvalType::Serial)) | ||
| .collect(); | ||
| self.runtime.storage().add_tree(&evaled) | ||
| } | ||
|
|
||
| fn eval_tree_parallel(&self, handle: Tree) -> Tree { | ||
| let tree = self.runtime.storage().get_tree(handle).unwrap(); | ||
| if tree.len() <= 3 { | ||
| return self.eval_tree_seq(handle); | ||
| } | ||
| let mut evaled = Vec::with_capacity(tree.len()); | ||
| // evaluate the first | ||
| evaled.push(self.eval_test(tree[0], EvalType::Serial)); | ||
| let mut tasks = Vec::with_capacity(tree.len() - 1); | ||
| for child in tree[1..].iter().copied() { | ||
| tasks.push(self.scheduler.push_work(child)); | ||
| } | ||
| for task in tasks { | ||
| evaled.push(self.wait_while_helping(&task)); | ||
| } | ||
| self.runtime.storage().add_tree(&evaled) | ||
| } | ||
| // elimnate redundancy i think | ||
| fn eval_test(&self, handle: Handle, eval_mode: EvalType) -> Handle { | ||
| //println!("evaluating {handle}"); | ||
| match handle { | ||
| Handle::Ref(reference) => self.eval(self.lift(Handle::Ref(reference))), | ||
| Handle::Thunk(_) => todo!(), | ||
| Handle::Object(obj) => match obj { | ||
| Object::Blob(blob) => blob.into(), | ||
| Object::Tree(tree) => self.eval_tree(tree, eval_mode).into(), | ||
| }, | ||
| Handle::Encode(e) => self.eval_test(self.encode(e, eval_mode), eval_mode), | ||
| } | ||
| } | ||
|
|
||
| pub fn eval(&self, handle: Handle) -> Handle { | ||
| self.eval_test(handle, EvalType::Parallel) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to rebase this on top of Haibib's changes, which I think simplify this code?
@Haibib thoughts?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll wait to rebase until Habib's new PR is approved and I'll push again!