RustBrock/The Performance Closures and Iterators.md
darkicewolf50 f3ec0d98cc
Some checks failed
Test Gitea Actions / first (push) Successful in 12s
Test Gitea Actions / check-code (push) Failing after 21s
Test Gitea Actions / test (push) Has been skipped
Test Gitea Actions / documentation-check (push) Has been skipped
finsihed ch14.1 and more than halfway through ch14.2
2025-02-25 17:02:50 -07:00

85 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.