Skip to content

Commit 373557e

Browse files
committed
clean up
1 parent 2024e12 commit 373557e

File tree

2 files changed

+35
-50
lines changed

2 files changed

+35
-50
lines changed

README.md

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,10 @@
11
### Introduction
22

3-
Welcome, Rustaceans! Dive into the core of Rust programming by exploring two fundamental concepts: pattern matching and dynamic dispatch. These features underpin the expressive power and flexibility of Rust, and understanding their nuances can greatly enhance your code's performance and maintainability.
3+
While building [Tailcall] we often internally have debates about the low-level design of our application. Pattern matching vs dynamic dispatch has been a common one. This repo intends to explore two fundamental concepts: **pattern matching** and **dynamic dispatch** and understand the actual difference in performance.
44

5-
### Pattern Matching
5+
[Tailcall]: https://github.com/tailcallhq/tailcall
66

7-
Pattern matching in Rust is a powerful feature that allows for concise and expressive handling of complex data structures. It lets you destructure data types, such as enums and tuples, directly in the match arms, enabling clear and straightforward data manipulation. This approach is particularly beneficial for handling nested data structures and offers precise control over program flow based on different data variants.
8-
9-
- **Use Cases**: Pattern matching shines in scenarios where you need to unpack and handle various data forms, especially when dealing with nested enums and error handling. For example, you can seamlessly match different message types and handle each specifically with minimal boilerplate.
10-
11-
### Dynamic Dispatch
12-
13-
Dynamic dispatch, on the other hand, provides flexibility by allowing a function call to be resolved at runtime based on the object type it is being called on. This is achieved using trait objects in Rust, which enable polymorphism. Dynamic dispatch is typically used when different behaviors for a common trait need to be executed depending on the runtime type.
14-
15-
- **Use Cases**: It's particularly useful in scenarios requiring high flexibility and extensibility from various object types, such as in UI frameworks or plugin systems where new types might be added without changing existing code.
16-
17-
### Comparison
18-
19-
| Feature | Pattern Matching | Dynamic Dispatch |
20-
| --------------- | ------------------------------ | ------------------------------- |
21-
| Dispatch Type | Compile-time (Static) | Runtime (Dynamic) |
22-
| Use Case | Structured data handling | Polymorphism with trait objects |
23-
| Performance | Generally faster and efficient | Less efficient, more flexible |
24-
| Code Complexity | Lower (with enums) | Higher (due to trait objects) |
25-
26-
- **Performance**: Pattern matching is compile-time and does not involve any indirection, making it faster and more efficient. Dynamic dispatch, while more flexible, incurs a performance cost due to runtime determination of method execution【6†source】【7†source】【8†source】【9†source】.
27-
28-
- **Flexibility**: Dynamic dispatch excels in scenarios where code must handle many different object types not known at compile time, at the expense of some performance due to virtual calls and inability to inline these calls【7†source】【9†source】.
29-
30-
### Practical Tips
31-
32-
1. **Pattern Matching**: Leverage the power of `match` statements to cleanly handle different cases of enums and error types. Use exhaustive matching to ensure all possible cases are handled.
33-
2. **Dynamic Dispatch**: Utilize trait objects when you need to operate on collections of objects of different types. Remember the trade-off between flexibility and performance.
34-
35-
### Conclusion
36-
37-
Both pattern matching and dynamic dispatch have their place in Rust programming. Choosing between them depends on the specific requirements of your application regarding performance and flexibility. By understanding and utilizing these features appropriately, you can write more efficient and maintainable Rust code.
38-
39-
Happy coding! 🦀
7+
Based on the benchmark results, pattern matching in Rust is significantly faster than dynamic dispatch (Not surprised!). The execution times indicate that pattern matching (around 320 picoseconds per operation) is roughly 72,000 times faster than dynamic dispatch (around 23 nanoseconds on the lower end of the spectrum).
408

419
```
4210
❯ cargo bench
@@ -64,3 +32,7 @@ Found 9 outliers among 100 measurements (9.00%)
6432
3 (3.00%) high mild
6533
6 (6.00%) high severe
6634
```
35+
36+
Happy coding! 🦀
37+
38+
PS: Feel free to raise a PR, if you think there is something wrong in the way the benchmarks are designed.

benches/benchmarks.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1-
use criterion::{criterion_group, criterion_main, Criterion};
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
22
use std::time::Duration;
33

44
trait DoSomething {
5-
fn do_it(&self);
5+
fn do_it(&self) -> i32;
66
}
77

88
struct ActionOne;
99
struct ActionTwo;
1010

1111
impl DoSomething for ActionOne {
1212
#[inline]
13-
fn do_it(&self) {
14-
// Simulate some work
15-
let _ = (1..10).fold(0, |acc, x| acc + x);
13+
fn do_it(&self) -> i32 {
14+
(1..10).fold(0, |acc, x| acc + x)
1615
}
1716
}
1817

1918
impl DoSomething for ActionTwo {
2019
#[inline]
21-
fn do_it(&self) {
22-
// Simulate some different work
23-
let _ = (1..10).map(|x| x * x).sum::<i32>();
20+
fn do_it(&self) -> i32 {
21+
(1..10).map(|x| x * x).sum::<i32>()
2422
}
2523
}
2624

@@ -29,29 +27,44 @@ enum Action {
2927
Two(ActionTwo),
3028
}
3129

32-
fn dynamic_dispatch() {
30+
fn dynamic_dispatch() -> i32 {
3331
let actions: Vec<Box<dyn DoSomething>> = vec![Box::new(ActionOne), Box::new(ActionTwo)];
32+
let mut output = 0;
3433
for action in actions {
35-
action.do_it();
34+
output = output + action.do_it();
3635
}
36+
output
3737
}
3838

39-
fn pattern_matching() {
39+
fn pattern_matching() -> i32 {
4040
let actions = vec![Action::One(ActionOne), Action::Two(ActionTwo)];
41+
let mut output = 0;
4142
for action in actions {
4243
match action {
43-
Action::One(a) => a.do_it(),
44-
Action::Two(a) => a.do_it(),
44+
Action::One(a) => output = output + a.do_it(),
45+
Action::Two(a) => output = output + a.do_it(),
4546
}
4647
}
48+
output
4749
}
4850

4951
fn benchmark(c: &mut Criterion) {
5052
let mut group = c.benchmark_group("Dispatch vs Matching");
5153
group.measurement_time(Duration::new(10, 0)); // Adjust the measurement time as needed
5254

53-
group.bench_function("Dynamic Dispatch", |b| b.iter(|| dynamic_dispatch()));
54-
group.bench_function("Pattern Matching", |b| b.iter(|| pattern_matching()));
55+
group.bench_function("Dynamic Dispatch", |b| {
56+
b.iter(|| {
57+
let output = dynamic_dispatch();
58+
black_box(output)
59+
})
60+
});
61+
62+
group.bench_function("Pattern Matching", |b| {
63+
b.iter(|| {
64+
let output = pattern_matching();
65+
black_box(output)
66+
})
67+
});
5568

5669
group.finish();
5770
}

0 commit comments

Comments
 (0)