You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/vision/status_quo/barbara_simulates_hydrodynamics.md
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -17,11 +17,11 @@ Her first attempt to was to emulate a common CFD design pattern: using message p
17
17
This solution worked, but Barbara had two problems with it. First, it gave her no control over CPU usage so the solution would greedily use all available CPU resources. Second, using messages to communicate solution values between patches did not scale when her team added a new feature (tracer particles) that added additional solution data the additional messages caused by this change created so much overhead that parallel processing was no faster than serial. So, Barbara decided to find a better solution.
18
18
19
19
### Solution Path
20
-
What Barbara wanted use the CPU more efficiently: she would decouple the work that needed to be done (the patches) from the workers (threads) this would allow her to more finely control how many resources were used. So, she began looking for a tool in Rust that would meet this design pattern. When she read about `async` and how it allowed the user to define units of work, called tasks, and send those to an executor which would manage the execution of those tasks across a set of workers, she thought she'd found exactly what she needed. Further reading indicate that `tokio` was the runtime of choice for `async` in the community and so she began building a new CFD tool with `async` and `tokio`. And to move away from the message passing design, because the number of messages being passed was proportional to the number of trace particles being traced.
20
+
To address the first problem: Barbara would decouple the work that needed to be done (solving each patch) from the workers (threads) this would allow her to more finely control how many resources were used. So, she began looking for a tool in Rust that would meet this design pattern. When she read about `async` and how it allowed the user to define units of work, called tasks, and send those to an executor which would manage the execution of those tasks across a set of workers, she thought she'd found exactly what she needed. Further reading indicate that `tokio` was the runtime of choice for `async` in the community and so she began building a new CFD tool with `async` and `tokio`. And to move away from the message passing design, because the number of messages being passed was proportional to the number of trace particles being traced.
21
21
22
22
As Barbara began working on her new design with `tokio`, her use of `async` went from a general (from the textbook) use of basic `async` features to a more specific implementation leveraging exactly the features that were most suited for her needs. At first, Barbara was under a false impression about what async executors do. She had assumed that a multi-threaded executor could automatically move the execution of an async block to a worker thread. When this turned out to wrong, she went to Stackoverflow and learned that async tasks must be explicitly spawned into a thread pool if they are to be executed on a worker thread. This meant that the algorithm to be parallelized became strongly coupled to both the spawner and the executor. Code that used to cleanly express a physics algorithm now had interspersed references to the task spawner, not only making it harder to understand, but also making it impossible to try different execution strategies, since with Tokio the spawner and executor are the same object (the Tokio runtime). Barbara felt that a better design for data parallelism would enable better separation of concerns: a group of interdependent compute tasks, and a strategy to execute them in parallel.
23
23
24
-
Along with moving the execution of the computational tasks to `async`, Barbara also used this as an opportunity to remove the message passing that was used to coordinate the computation of each patch. She used the `async` API to define dependencies between patches so that a patch would only begin computing its solution when its neighboring patches had completed. This also required setting up shared state that would store the solutions for all the patches as they were computed, so that dependents could access them. Learning how to properly use shared data with `async` was a new challenge. The initial design:
24
+
With the move to `async`, Barbara saw an opportunity to solve her second program. Rather than using message passing to coordinate patch computation, she used the `async` API to define dependencies between patches so that a patch would only begin computing its solution when its neighboring patches had completed. She setup a shared data structure to track the solutions for each patch now that messages would not be passing that data. Learning how to properly use shared data with `async` was a new challenge. The initial design:
@@ -34,7 +34,7 @@ Along with moving the execution of the computational tasks to `async`, Barbara a
34
34
```
35
35
lacked performance because she needed to clone the value for every task. So, Barbara switched over to using `Arc` to keep a thread safe RC to the shared data. But this change introduced a lot of `.map` and `.unwrap` function calls, making the code much harder to read. She realized that managing the dependency graph was not intuitive when using `async` for concurrency.
36
36
37
-
During the move to `async` Barbara ran into a steep learning curve with error handling. The initial version of her design just used `panic!`s to fail the program if an error was encountered, but the stack traces were almost unreadable. She asked her teammate Grace to migrate over to using the `Result` idiom for error handling and Grace found a major inconvenience. The Rust type inference inconsistently breaks when propagating `Result` in `async` blocks. Grace frequently found that she had to specify the type of the error when creating a result value:
37
+
A new problem arose during the move to `async`: a steep learning curve with error handling. The initial version of her design used `panic!`s to fail the program if an error was encountered, but the stack traces were almost unreadable. She asked her teammate Grace to migrate over to using the `Result` idiom for error handling and Grace found a major inconvenience. The Rust type inference inconsistently breaks when propagating `Result` in `async` blocks. Grace frequently found that she had to specify the type of the error when creating a result value:
0 commit comments