Hi everybody,
I'm trying to find the min and the max or a particular combination of two matrices. Let's say I have a matrix a with shape (10000, 800) and a matrix b with shape (150, 800). I now want to broadcast each row of a onto b, then perform component-wise multiplication (the Hadamard product) and subsequently find the overal min and max value.
The idiomatic code that I came up with is:
pub fn min_max_component_wise(
a: &ndarray::Array2<f32>,
b: &ndarray::Array2<f32>,
) -> Option<(f32, f32)> {
let (mut min, mut max) = (f32::MAX, f32::MIN);
for row in a.axis_iter(ndarray::Axis(0)) {
ndarray::Zip::from(b)
.and_broadcast(row)
.apply(|w, s| {
let val = w * s;
min = val.min(min);
max = val.max(max);
});
}
Some((min, max))
}
This, however, is quite slow. To speed it up, I resorted to directly indexing into the second array. This already made it quite a bit faster:
pub fn min_max_component_wise(
a: &ndarray::Array2<f32>,
b: &ndarray::Array2<f32>,
) -> Option<(f32, f32)> {
let (mut min, mut max) = (f32::MAX, f32::MIN);
for ((_, c), &sample) in a.indexed_iter() {
for w in 0..b.shape()[0] {
let val = sample * b[[w, c]];
if val < min {
min = val;
}
if val > max {
max = val;
}
}
}
Some((min, max))
}
Do you see any other opportunities to make this run faster? I'm using the following set of of benchmarks to test this:
use criterion::{criterion_group, criterion_main, Criterion};
use ndarray::Array;
use ndarray_rand::rand_distr::Uniform;
use ndarray_rand::RandomExt;
fn custom_criterion() -> Criterion {
Criterion::default().sample_size(20)
}
fn bench_component_wise_small(c: &mut Criterion) {
let s = Array::random((1000, 64), Uniform::new(0., 10.));
let w = Array::random((32, 64), Uniform::new(0., 10.));
c.bench_function("min_max_component_wise_small", |b| {
b.iter(|| min_max_component_wise(&s, &w))
});
}
fn bench_component_wise_large(c: &mut Criterion) {
let s = Array::random((1000, 784), Uniform::new(0., 10.));
let w = Array::random((144, 784), Uniform::new(0., 10.));
c.bench_function("min_max_component_wise_large", |b| {
b.iter(|| min_max_component_wise(&s, &w))
});
}
criterion_group! {
name = benches;
config = custom_criterion();
targets = bench_component_wise_small, bench_component_wise_large,
}
criterion_main!(benches);
Thank you very much in advance!