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
@@ -19,9 +19,9 @@ This solution worked, but Niklaus had two problems with it. First, it gave him n
19
19
### Solution Path
20
20
To address the first problem: Niklaus' new design decoupled the work that needed to be done (solving physics equations for each patch in the grid) from the workers (threads), this would allow him to set the number of threads and not use all the CPU resources. So, he began looking for a tool in Rust that would meet this design pattern. When he read about `async` and how it allowed the user to define units of work and send those to an executor which would manage the execution of those tasks across a set of workers, he thought he'd found exactly what he needed. He also thought that the `.await` semantics would give a much better way of coordinating dependencies between patches. Further reading indicated that `tokio` was the runtime of choice for `async` in the community and, so, he began building a new CFD solver with `async` and `tokio`.
21
21
22
-
As Niklaus began working on his new design with `tokio`, his 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 his needs. At first, Niklaus was under a false impression about what `async` executors do. He 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, he 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). Niklaus 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.
22
+
After making some progress, Niklaus ran into his firts problem. Niklaus had been under a false impression about what `async` executors do. He 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, he 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). Niklaus 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
-
With the move to `async`, Niklaus saw an opportunity to solve his second program. Rather than using message passing to coordinate patch computation, he 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. He 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:
24
+
Niklaus second problem came as he tried to fully replace the message passing from the first design: sharing data between tasks. He used the `async` API to coordinate computation of patches so that a patch would only go to a worker when all its dependencies had completed. But he also needed to account for the solution data which was passed in the messages. He 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 @@ With the move to `async`, Niklaus saw an opportunity to solve his second program
34
34
```
35
35
lacked performance because he needed to clone the value for every task. So, Niklaus 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. He realized that managing the dependency graph was not intuitive when using `async` for concurrency.
36
36
37
-
A new problem arose during the move to `async`: a steep learning curve with error handling. The initial version of his design used `panic!`s to fail the program if an error was encountered, but the stack traces were almost unreadable. He asked his 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
+
As the program matured, a new problem arose: a steep learning curve with error handling. The initial version of his design used `panic!`s to fail the program if an error was encountered, but the stack traces were almost unreadable. He asked his 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