I am new to Rust and am trying to benchmark sorting algorithms.
The functions take immutable references to a slice (fn sort(&self, slice: &mut [T])).
I am trying to use the criterion crate to benchmark them for different inputs (random vectors of increasing length).
However I cannot figure out how to pass the vectors to the functions as mutable references. I have tried the the fixes suggested by rustc but each generates another error.
The benchmark code is (shortened example to exclude all the functions):
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use rand::{distributions::Uniform, Rng};
use sorting::*;
fn sorting_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("Sorting");
for i in [1, 10, 100].iter() {
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 100);
let nums: Vec<usize> = (0..(*i)).map(|_| rng.sample(&range)).collect();
let mut v = black_box(nums.to_vec());
group.bench_with_input(BenchmarkId::new("Bubble Sort {}", i), &v, |b, v| {
b.iter(|| BubbleSort.sort(v))
});
group.finish();
}
The function for BubbleSort.sort is:
impl<T> Sorter<T> for BubbleSort {
fn sort(&self, arr: &mut [T])
where
T: Ord,
{
let mut swapped = true;
while swapped {
swapped = false;
for i in 1..arr.len() {
if arr[i - 1] > arr[i] {
arr.swap(i - 1, i);
swapped = true;
}
}
}
}
}
Some examples of the error messages.
First:
error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable
--> benches/criterion_bench.rs:16:39
|
15 | group.bench_with_input(BenchmarkId::new("Bubble Sort {}", i), &v, |b, v| {
| - help: consider changing this to be mutable: `mut v`
16 | b.iter(|| BubbleSort.sort(&mut v))
| ^^^^^^ cannot borrow as mutable
If I do the suggested fix, I still get cannot borrow as mutable.
I have successfully benchmarked the functions using bench_function from the criterion crate. But this does not help me with benchmarking against multiple inputs for comparison.
The direct problem in your code is that you used &v where you needed &mut v. But that's not actually going to work well, and you should not be using bench_with_input for this purpose. (In fact, I'm not sure what bench_with_input is good for — closure captures work just fine.)
The key thing to understand is that Criterion is going to run your benchmarked function many times (when you call iter). If you did arrange to pass a mutable reference to the same vector for each run, that would not be appropriate for benchmarking a sorting algorithm because it means all iterations but the first would receive already-sorted input. When the performance of the algorithm depends on the order of the input, you want a fresh non-sorted vector each time.
When you want to benchmark a function that consumes or mutates input, you use a different Bencher method than iter(). In particular, iter_batched_ref() is most appropriate for benchmarking a sorting algorithm (or any other function which takes a mutable reference and mutates it).
fn sorting_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("Sorting");
for i in [1, 10, 100] {
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 100);
group.bench_function(BenchmarkId::new("Bubble Sort {}", i), |b| {
b.iter_batched_ref(
|| -> Vec<usize> { (0..i).map(|_| rng.sample(&range)).collect() },
|v| BubbleSort.sort(v),
BatchSize::SmallInput,
)
});
}
group.finish();
}
Notice that the input vector is constructed inside of a function passed to iter_batched_ref. iter_batched_ref will call that function several times, then call the function being benchmarked several times to measure it, with a fresh input each time.
Related
I have an RgbImage but for the gaussian blur algorithm I need to convert it to Vec<[u8; 3]>. I managed to do this before using:
let img: Vec<u8> = img.into_raw();
let mut new: Vec<[u8; 3]> = vec![];
for (r, g, b) in img.into_iter().tuples() {
new.push([r, g, b]);
}
But in my new project for some reason I get the error no method named tuples found for struct std::vec::IntoIter in the current scope. I'm not sure why this is happening but I think there is a much better way to do it anyway.
You need to add the crate itertools for tuples().
Or you can use std's chunks_exact():
for color in img.chunks_exact(3) {
new.push(color.try_into().unwrap());
}
I would like to iterate through rows of an Array2 and items of an Array1 in parallel and do some computation on them with side effects.
I tried something like below,
extern crate ndarray;
extern crate ndarray_parallel;
use ndarray::{Array2, Array, Zip, Axis};
use ndarray_parallel::prelude::*;
fn main() {
let mut a = Array2::<f64>::zeros((5, 5));
let b = Array::from_iter(0..5);
let c = vec![1,2,3,4,5];
let mut d = vec![1,2,3,4,5];
let z = Zip::from(a.axis_iter(Axis(0))).and(&b);
z.par_apply(|x,y| {d[*y as usize] = 10});
}
But the compiler is complaining.
Can anyone advise?
Multiple misconceptions in your code, correct me if any of the followings is not what you want:
b seems to serve as index to the vector. You can use Zip::indexed to produce index along with element directly, no need for an explicit index array.
axis_iter returns an iterator which traverses all axis of underlying ndarray. You probably want index_axis to traverse one of them.
Your arrays host both integers and floats. In rust you can't apply arithmetic operations between the two.
par_apply takes a Fn, so it can't mutate captured variables.
All things considered, the code probably should look like this:
use ndarray::{Array2, Zip, Axis};
use ndarray_parallel::prelude::*;
fn main() {
let a = Array2::<f64>::zeros((5, 5));
let mut d = vec![1.,2.,3.,4.,5.];
Zip::indexed(a.index_axis(Axis(0), 0))
.and(&mut d)
.par_apply(|_i, x, y| *y = x + 10.);
}
I'm struggling to find all direct neighbours for a given position (which is a (usize, usize)) in an N x M matrix.
The matrix provides a method get(p: Position) -> Option<T> where it does bounds checking and returns None if the position is out of range. This means an upper bound does not need to be checked, but a lower bound still needs to. That is, it does not underflow 0 in any direction.
My first attempt was to iterate over the slice &[-1, 0, 1] and use checked_add, but this does not work because -1 is not a usize.
My next attempt was to subtract 1 from my position (both x and y) and then iterate over the slice &[0, 1, 2], but that does not work the position (0, _) or (_, 0).
I prepared a playground, where one just can implement the one function. (I hope my tests are correct)
type Position = (usize, usize) //x, y
fn get_neighbours(p: Position) -> impl Iterator<Item = Position> {
unimplemented!()
}
(impl Iterator can be replaced by Vec if necessary).
You just need to use if expressions to handle the special case that the coordinates are zero, e.g.
fn get_neighbours(p: Position) -> impl Iterator<Item = Position> {
let m_range = if p.0 > 0 { p.0 - 1..p.0 + 2 } else { 0..2 };
let n_range = if p.1 > 0 { p.1 - 1..p.1 + 2 } else { 0..2 };
m_range
.flat_map(move |m| n_range.clone().map(move |n| (m, n)))
.filter(move |&q| p != q)
}
I would like to take random samples from very large lists while maintaining the order. I wrote the script below, but it requires .map(idx => ls(idx)) which is very wasteful. I can see a way of making this more efficient with a helper function and tail recursion, but I feel that there must be a simpler solution that I'm missing.
Is there a clean and more efficient way of doing this?
import scala.util.Random
def sampledList[T](ls: List[T], sampleSize: Int) = {
Random
.shuffle(ls.indices.toList)
.take(sampleSize)
.sorted
.map(idx => ls(idx))
}
val sampleList = List("t","h","e"," ","q","u","i","c","k"," ","b","r","o","w","n")
// imagine the list is much longer though
sampledList(sampleList, 5) // List(e, u, i, r, n)
EDIT:
It appears I was unclear: I am referring to maintaining the order of the values, not the original List collection.
If by
maintaining the order of the values
you understand to keeping the elements in the sample in the same order as in the ls list, then with a small modification to your original solution the performances can be greatly improved:
import scala.util.Random
def sampledList[T](ls: List[T], sampleSize: Int) = {
Random.shuffle(ls.zipWithIndex).take(sampleSize).sortBy(_._2).map(_._1)
}
This solution has a complexity of O(n + k*log(k)), where n is the list's size, and k is the sample size, while your solution is O(n + k * log(k) + n*k).
Here is an (more complex) alternative that has O(n) complexity. You can't get any better in terms of complexity (though you could get better performance by using another collection, in particular a collection that has a constant time size implementation). I did a quick benchmark which indicated that the speedup is very substantial.
import scala.util.Random
import scala.annotation.tailrec
def sampledList[T](ls: List[T], sampleSize: Int) = {
#tailrec
def rec(list: List[T], listSize: Int, sample: List[T], sampleSize: Int): List[T] = {
require(listSize >= sampleSize,
s"listSize must be >= sampleSize, but got listSize=$listSize and sampleSize=$sampleSize"
)
list match {
case hd :: tl =>
if (Random.nextInt(listSize) < sampleSize)
rec(tl, listSize-1, hd :: sample, sampleSize-1)
else rec(tl, listSize-1, sample, sampleSize)
case Nil =>
require(sampleSize == 0, // Should never happen
s"sampleSize must be zero at the end of processing, but got $sampleSize"
)
sample
}
}
rec(ls, ls.size, Nil, sampleSize).reverse
}
The above implementation simply iterates over the list and keeps (or not) the current element according to a probability which is designed to give the same chance to each element. My logic may have a flow, but at first blush it seems sound to me.
Here's another O(n) implementation that should have a uniform probability for each element:
implicit class SampleSeqOps[T](s: Seq[T]) {
def sample(n: Int, r: Random = Random): Seq[T] = {
assert(n >= 0 && n <= s.length)
val res = ListBuffer[T]()
val length = s.length
var samplesNeeded = n
for { (e, i) <- s.zipWithIndex } {
val p = samplesNeeded.toDouble / (length - i)
if (p >= r.nextDouble()) {
res += e
samplesNeeded -= 1
}
}
res.toSeq
}
}
I'm using it frequently with collections > 100'000 elements and the performance seems reasonable.
It's probably the same idea as in Régis Jean-Gilles's answer but I think the imperative solution is slightly more readable in this case.
Perhaps I don't quite understand, but since Lists are immutable you don't really need to worry about 'maintaining the order' since the original List is never touched. Wouldn't the following suffice?
def sampledList[T](ls: List[T], sampleSize: Int) =
Random.shuffle(ls).take(sampleSize)
While my previous answer has linear complexity, it does have the drawback of requiring two passes, the first one corresponding to the need to compute the length before doing anything else. Besides affecting the running time, we might want to sample a very large collection for which it is not practical nor efficient to load the whole collection in memory at once, in which case we'd like to be able to work with a simple iterator.
As it happens, we don't need to invent anything to fix this. There is simple and clever algorithm called reservoir sampling which does exactly this (building a sample as we iterate over a collection, all in one pass). With a minor modification we can also preserve the order, as required:
import scala.util.Random
def sampledList[T](ls: TraversableOnce[T], sampleSize: Int, preserveOrder: Boolean = false, rng: Random = new Random): Iterable[T] = {
val result = collection.mutable.Buffer.empty[(T, Int)]
for ((item, n) <- ls.toIterator.zipWithIndex) {
if (n < sampleSize) result += (item -> n)
else {
val s = rng.nextInt(n)
if (s < sampleSize) {
result(s) = (item -> n)
}
}
}
if (preserveOrder) {
result.sortBy(_._2).map(_._1)
}
else result.map(_._1)
}
I've come across the following recursive algorithm, written here in Swift, that given an array, produces a generator that generates sub-arrays that are one element shorter than the original array. The sub arrays are created by removing one element at every index.
ie input [1,2,3] would return a generator that generated [1,2] [2,3] [1,3].
The algorithm works, but I'm having real trouble understanding how. Could someone explain what's happening, or offer advice on how to analyze or understand it? Thanks in advance
// Main algorithm
func smaller1<T>(xs:[T]) -> GeneratorOf<[T]> {
if let (head, tail) = xs.decompose {
var gen1:GeneratorOf<[T]> = one(tail)
var gen2:GeneratorOf<[T]> = map(smaller1(tail)) {
smallerTail in
return [head] + smallerTail
}
return gen1 + gen2
}
return one(nil)
}
// Auxillary functions used
func map<A, B>(var generator:GeneratorOf<A>, f:A -> B) -> GeneratorOf<B> {
return GeneratorOf {
return generator.next().map(f)
}
}
func one<X>(x:X?) -> GeneratorOf<X> {
return GeneratorOf(GeneratorOfOne(x))
}
The code is taken from the book 'Functional Programming in Swift' by Chris Eidhof, Florian Kugler, and Wouter Swierstra
Given an array [a_1,…,a_n], the code:
Generates the sub-array [a_2,…,a_n];
For each sub-array B of [a_2,…,a_n] (generated recursively), generates [a_1] + B.
For example, given the array [1,2,3], we:
Generate [2,3];
For each sub-array B of [2,3] (namely, [3] and [2]), generate [1] + B (this generates [1,3] and [1,2]).