finished ch13.4 and finished ch13
All checks were successful
Test Gitea Actions / first (push) Successful in 13s

This commit is contained in:
darkicewolf50 2025-02-24 17:06:04 -07:00
parent d961dd4ee9
commit c47bd9cfae
2 changed files with 96 additions and 1 deletions

View File

@ -17,4 +17,15 @@ It will cover:
We have already covered some other Rust freatures, such as pattern matchin and enums, that are also influenced by the functional style.
Mastering closures and iterators is an improtant part of wiritng idiomatic, fast Rust code.
Mastering closures and iterators is an improtant part of wiritng idiomatic, fast Rust code.
## Summary
Closures and iterators are Rust features inspired by functional programming language ideas.
They contribute to the capability to express high-level ideas at low-level preformance.
The implementations of closures and iterators are such that runtime performance is not affected.
This is part of Rust's goal to strive to provide zero-cost abstractions.

View File

@ -0,0 +1,84 @@
# Comparing Performance: Loops vs. Iterators
To determince whether to use loops or iterators, you need to know which implementation is faster
Is it the version with an explicit `for` loop?
Is it the version with iterators?
Here is a benchmark where we load the entire contnets of *The Adventures of Sherlock Holmes* by Sir Arthur Conan Doyle into a `String` and looking ofr the word *the* in the contents
Here is the results of the benchmark on the version of `search` using the `for` loop and the version using iterators:
```
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
```
The two have similar performace!
We won't explain the benchmark code here, because the poin is not to prive that the two versions are equivalent but to get a general sense of how these two compare performance-wise.
For a more comprehensive benchmark, you should check using various texts of various sizes as the `contents`, different words and words of different lenghts as the `query` and all kinds of other variations.
The point is this, iterators, although a high-level abstraction, get compiled down to roughly the same code as if you wrote the lower-level code yourself.
Iterators are one of Rust's *zero-cost abstractions`. This means using the abstraction imposes no addtional runtime overhead.
This is analogous to how Bjarne Stroustrup, the original designer and implementor of C++ defines *zero-verhead* in "Fondations of C++" (2012)
```
In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for. And further: What you do use, you couldnt hand code any better.
```
Here is another example, the code is taken from an audio decoder.
The decoding algorithm uses the linear prediction mathematical operation to estimate future values based on a linear function of the previous samples.
This code uses an iterator chain to do some math on three variables in scope:
- a `buffer` slice of data
- an array of `coefficients`
- an amount ot which shift data in `qlp_shift`
We declared the variables within this example but not given them any values.
Even though this code doesnt have muc meaning outside of its context, it is still a concise, real-world example of how Rust tranlate high-level ideas to low-level code.
```rust
let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;
for i in 12..buffer.len() {
let prediction = coefficients.iter()
.zip(&buffer[i - 12..i])
.map(|(&c, &s)| c * s as i64)
.sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}
```
To calcuate the value of `prediction`, the code iterates though each of he 12 values in `coefficients` and ses the `zip` method to pair the coefficient values with the previous 12 values in `buffer`
For each pair then we multiply the values together, sum all the results and shift the bits in the sum `qlp_shift` bits to the right.
Calculations in applications like audio decoders often prioritize perfomance.
Here we are creating an iterator, using two adapters and then consuming the value.
What assembly would this Rust code compile into.
As of writing this in the book, it compiles down to the same assbly you would write by hand.
There is no loop at all corresponding to the iteration over the values in `coefficients`.
Rust knows that there are 12 iterations, so it "unrolls" the loop.
*Unrolling* is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration fo the loop.
All of the coefficients ge stored in registers which means accessing the values is very fast.
There are no bounds checks on the array access at runtime.
All of these optimizations that Rust is able to apply make the resulting code extremely efficient.
Now you know that you can use iterators and closures without fear of performance hits.
They make code seem like its highe level but again dont impose a performance hit.