diff --git a/.gitignore b/.gitignore index ea8c4bf..5f9ff5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.vscode/ \ No newline at end of file diff --git a/DESIGN.md b/DESIGN.md index c43a407..93f57e6 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -6,6 +6,11 @@ memory allocator and threading provided by the rust standard library. We are not building a new erlang/BEAM. Minimal feature set means spawning actor processes, not having supervisiors, lots of process monitoring tools, prempting, etc. +# FIXME +This is slightly obsolete. Was necessary in order to concentrate on getting the basic skeleton up, now it's a distraction and unreliable. +After getting the benches/etc finished, move this into an `ARCHITECTURE.md` file, have it make sense. + + ## Actor model An actor has: @@ -48,30 +53,3 @@ The router is the engine for message delivery. It posesses: - Its own inbox: The router possesses its own mpsc queue where references to messages are stored. The router will process this queue by dereferencing and writing directly into the recipient's inbox buffer. -### Misc - -A means of providing an emergency overflow without adding much more code complexity. The mutex means -this will not be `no_std` however. - -```rust -struct HybridChannel { - // Start with lock-free ring buffer - ring: AtomicRingBuffer, - - // When full, spill into a Mutex> - overflow: parking_lot::Mutex>, - - // Track overflow frequency to resize ring proactively - overflow_count: AtomicUsize, -} - -impl HybridChannel { - fn push(&self, value: T) { - if self.ring.push(value).is_err() { - self.overflow.lock().push_back(value); - self.overflow_count.fetch_add(1, Relaxed); - // Optionally: if overflow_count > threshold, grow ring - } - } -} -``` diff --git a/TODOs.md b/TODOs.md new file mode 100644 index 0000000..4e9885c --- /dev/null +++ b/TODOs.md @@ -0,0 +1,44 @@ +### Profiling and Benchmarking + +- Research as to modern art on benching and profiling + - Implement an MVP here. + - Bench/profile against a suite of tests selected for generality across actor framework usecases + +- Identify hot paths and bottlenecks + - e.g. pretty sure the router is a major bottleneck, what else + - follow through the entire message cycle: + Parent process -> Convert to swactor::Message/Envelope -> Router -> Delivery -> Processing -> etc. + Identify every small detail on which you may be able to improve, any unneeded processing or branching + +- (optional) Visualization tools: + - make some pretty stuff for tracing messages, actor activity, router activity, etc. + +### Usage + +- After benching and profiling, cleaning up the most egregious wrongdoings we will: + - actually implement our own projects in the framework, ones I actually find useful personally + +- Optimization pipeline: + - Once we have well-established benches and profiles for general cases, build a set of tools that can auto-optimize + for given use cases. Tuning, for example, the channel buffers, router behavior, message consumption behavior, etc. + +### Chores + +- go over all the FIXMEs littered about. Add comments. +- add misc features as they come up. Prefer tools for understanding execution flows, visualizing flows, and adding +robustness, over ergonomics. Better to be slightly clunky but fast and optimized, than vice versa. + +### Far future + +- make language bindings. e.g., an npm package, python bindings, etc. + +### Optimization + +- Localize actors and inboxes: + - Because the entire runtime is message driven, the happy path must be fast. Even lock free, when we have to go through + several calls of an atomic ring buffer in order to process a single message, its unnecessary. + Parent process -> router -> actor -> router -> inbox -> parent process; every transfer going through an atomic buffer. + + - to do this, design heavily around a localized worker thread. Actors on a working thread should have their inbox localized, they + should be 'sticky' to that thread (FILO queue?), and we should route messages based on core locality. Future optimizations can include + a tunable algorithm that puts actors that frequently communicate together on the same thread. \ No newline at end of file diff --git a/src/actor.rs b/src/actor.rs index 3450bee..c3938ea 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -87,6 +87,7 @@ where fn tick(&mut self, ctx: &Runtime) { // TODO: WATERLEVEL is hard coded, and so is this message handling scheme. We should // make it so both are more flexible, with sane defaults. + // FIXME: lots of indirection just to get length on a hot path let total_messages = self.inbox.len(); let messages_to_process = if total_messages < WATERLEVEL { total_messages diff --git a/src/router.rs b/src/router.rs index 55296f0..34157f4 100644 --- a/src/router.rs +++ b/src/router.rs @@ -19,6 +19,14 @@ pub(crate) trait SenderT: Send + Sync { impl SenderT for Sender { fn try_send(&self, envelope: Envelope) { if let Some(msg) = envelope.downcast_ref::() { + // FIXME: we are directly cloning the contents of the Arc pointer here + // Do we want to? Should we provide another way? + // + // The standard concept of an actor has message and state + // isolation, so we should leave this as is. However, we should + // make it clear and obvious this pathway is the heavy, contained + // pathway, and include a shared memory pathway for logic that + // may need it. let _ = Sender::try_send(self, msg.clone()); } }