Skip to content

Commit

Permalink
Merge pull request #1 from jvdd/refactoring
Browse files Browse the repository at this point in the history
✨ support f16 + 🧹 some minor refactoring
  • Loading branch information
jvdd authored Nov 5, 2022
2 parents f75bc79 + f7e3097 commit f2d036f
Show file tree
Hide file tree
Showing 24 changed files with 710 additions and 397 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
name: argminmax test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ['windows-latest', 'macOS-latest', 'ubuntu-latest']
rust: ['stable', 'beta', 'nightly']

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
components: clippy, rustfmt

- name: Rust toolchain info
run: |
cargo --version --verbose
rustc --version
cargo clippy --version
cargo fmt --version
- name: Linting
run: |
cargo fmt -- --check
cargo clippy --features half -- -D warnings
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1

- name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1
with:
args: '--features half -- --test-threads 1'

- name: Upload to codecov.io
uses: codecov/codecov-action@v3
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "argminmax"
version = "0.1.1"
version = "0.2.0"
authors = ["Jeroen Van Der Donckt"]
edition = "2021"
readme = "README.md"
Expand All @@ -12,13 +12,18 @@ categories = ["algorithms", "mathematics", "science"]


[dependencies]
ndarray = "0.15.6"
ndarray = { version = "0.15.6", default-features = false }
half = { version = "2.1.0", default-features = false, optional = true }

[dev-dependencies]
criterion = "0.3.0"
dev_utils = { path = "dev_utils" }


[[bench]]
name = "bench_f16"
harness = false

[[bench]]
name = "bench_f32"
harness = false
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# ArgMinMax
> Efficient argmin & argmax (in 1 function) with SIMD (avx2) for `f32`, `f64`, `i16`, `i32`, `i64` on `ndarray::ArrayView1`
> Efficient argmin & argmax (in 1 function) with SIMD (avx2) for `f16`, `f32`, `f64`, `i16`, `i32`, `i64` on `ndarray::ArrayView1`
<!-- This project uses [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) [avx2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2) (256 bit registers) to compute argmin and argmax in a single function. -->

🚀 The function is generic over the type of the array, so it can be used on an `ndarray::ArrayView1<T>` where `T` can be `f32`, `f64`, `i16`, `i32`, `i64`.
🚀 The function is generic over the type of the array, so it can be used on an `ndarray::ArrayView1<T>` where `T` can be `f16`*, `f32`, `f64`, `i16`, `i32`, `i64`.

👀 Note that this implementation contains no if checks, ensuring that the runtime of the function is independent of the input data its order (best-case = worst-case = average-case).

*for `f16` you should enable the 'half' feature.

## Installing

Add the following to your `Cargo.toml`:
Expand All @@ -25,7 +27,7 @@ use ndarray::Array1;
let arr: Vec<i32> = (0..200_000).collect();
let arr: Array1<i32> = Array1::from(arr);

let (min, max) = arr.view().argminmax().unwrap(); // apply extension
let (min, max) = arr.view().argminmax(); // apply extension

println!("min: {}, max: {}", min, max);
println!("arr[min]: {}, arr[max]: {}", arr[min], arr[max]);
Expand All @@ -41,7 +43,14 @@ See `/benches/results`.

Run the benchmarks yourself with the following command:
```bash
cargo bench --quiet --message-format=short | grep "time:"
cargo bench --quiet --message-format=short --features half | grep "time:"
```

## Tests

To run the tests use the following command:
```bash
cargo test --message-format=short --features half
```

---
Expand Down
90 changes: 90 additions & 0 deletions benches/bench_f16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#[macro_use]
extern crate criterion;
extern crate dev_utils;

#[cfg(feature = "half")]
use argminmax::ArgMinMax;
use criterion::{black_box, Criterion};
use dev_utils::{config, utils};

#[cfg(feature = "half")]
use half::f16;
use ndarray::Array1;

#[cfg(feature = "half")]
fn get_random_f16_array(n: usize) -> Array1<f16> {
let data = utils::get_random_array::<u16>(n, u16::MIN, u16::MAX);
let data: Vec<f16> = data.iter().map(|&x| f16::from_bits(x)).collect();
// Replace NaNs and Infs with 0
let data: Vec<f16> = data
.iter()
.map(|&x| {
if x.is_nan() || x.is_infinite() {
f16::from_bits(0)
} else {
x
}
})
.collect();
let arr: Array1<f16> = Array1::from(data);
arr
}

#[cfg(feature = "half")]
fn minmax_f16_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = get_random_f16_array(n);
c.bench_function("simp_random_long_f16", |b| {
b.iter(|| argminmax::scalar_argminmax_f16(black_box(data.view())))
});
c.bench_function("simd_random_long_f16", |b| {
b.iter(|| black_box(data.view().argminmax()))
});
}

#[cfg(feature = "half")]
fn minmax_f16_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = get_random_f16_array(n);
c.bench_function("simple_random_short_f16", |b| {
b.iter(|| argminmax::scalar_argminmax_f16(black_box(data.view())))
});
c.bench_function("simd_random_short_f16", |b| {
b.iter(|| black_box(data.view().argminmax()))
});
}

#[cfg(feature = "half")]
fn minmax_f16_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<f16>(n, f16::from_f32(1.));
c.bench_function("simple_worst_long_f16", |b| {
b.iter(|| argminmax::scalar_argminmax_f16(black_box(data.view())))
});
c.bench_function("simd_worst_long_f16", |b| {
b.iter(|| black_box(data.view().argminmax()))
});
}

#[cfg(feature = "half")]
fn minmax_f16_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<f16>(n, f16::from_f32(1.));
c.bench_function("simple_worst_short_f16", |b| {
b.iter(|| argminmax::scalar_argminmax_f16(black_box(data.view())))
});
c.bench_function("simd_worst_short_f16", |b| {
b.iter(|| black_box(data.view().argminmax()))
});
}

#[cfg(feature = "half")]
criterion_group!(
benches,
minmax_f16_random_array_long,
minmax_f16_random_array_short,
minmax_f16_worst_case_array_long,
minmax_f16_worst_case_array_short
);
#[cfg(feature = "half")]
criterion_main!(benches);
8 changes: 4 additions & 4 deletions benches/bench_f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn minmax_f32_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_random_array::<f32>(n, f32::MIN, f32::MAX);
c.bench_function("simple_random_long_f32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_long_f32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -21,7 +21,7 @@ fn minmax_f32_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_random_array::<f32>(n, f32::MIN, f32::MAX);
c.bench_function("simple_random_short_f32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_short_f32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -32,7 +32,7 @@ fn minmax_f32_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<f32>(n, 1.0);
c.bench_function("simple_worst_long_f32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_long_f32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -43,7 +43,7 @@ fn minmax_f32_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<f32>(n, 1.0);
c.bench_function("simple_worst_short_f32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_short_f32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand Down
8 changes: 4 additions & 4 deletions benches/bench_f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn minmax_f64_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_random_array::<f64>(n, f64::MIN, f64::MAX);
c.bench_function("simple_random_long_f64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_long_f64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -21,7 +21,7 @@ fn minmax_f64_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_random_array::<f64>(n, f64::MIN, f64::MAX);
c.bench_function("simple_random_short_f64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_short_f64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -32,7 +32,7 @@ fn minmax_f64_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<f64>(n, 1.0);
c.bench_function("simple_worst_long_f64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_long_f64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -43,7 +43,7 @@ fn minmax_f64_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<f64>(n, 1.0);
c.bench_function("simple_worst_short_f64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_short_f64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand Down
8 changes: 4 additions & 4 deletions benches/bench_i16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn minmax_i16_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_random_array::<i16>(n, i16::MIN, i16::MAX);
c.bench_function("simple_random_long_i16", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_long_i16", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -21,7 +21,7 @@ fn minmax_i16_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_random_array::<i16>(n, i16::MIN, i16::MAX);
c.bench_function("simple_random_short_i16", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_short_i16", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -32,7 +32,7 @@ fn minmax_i16_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<i16>(n, 1);
c.bench_function("simple_worst_long_i16", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_long_i16", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -43,7 +43,7 @@ fn minmax_i16_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<i16>(n, 1);
c.bench_function("simple_worst_short_i16", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_short_i16", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand Down
8 changes: 4 additions & 4 deletions benches/bench_i32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn minmax_i32_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_random_array::<i32>(n, i32::MIN, i32::MAX);
c.bench_function("simple_random_long_i32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_long_i32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -21,7 +21,7 @@ fn minmax_i32_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_random_array::<i32>(n, i32::MIN, i32::MAX);
c.bench_function("simple_random_short_i32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_short_i32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -32,7 +32,7 @@ fn minmax_i32_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<i32>(n, 1);
c.bench_function("simple_worst_long_i32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_long_i32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -43,7 +43,7 @@ fn minmax_i32_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<i32>(n, 1);
c.bench_function("simple_worst_short_i32", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_short_i32", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand Down
8 changes: 4 additions & 4 deletions benches/bench_i64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn minmax_i64_random_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_random_array::<i64>(n, i64::MIN, i64::MAX);
c.bench_function("simple_random_long_i64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_long_i64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -21,7 +21,7 @@ fn minmax_i64_random_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_random_array::<i64>(n, i64::MIN, i64::MAX);
c.bench_function("simple_random_short_i64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_random_short_i64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -32,7 +32,7 @@ fn minmax_i64_worst_case_array_long(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_LONG;
let data = utils::get_worst_case_array::<i64>(n, 1);
c.bench_function("simple_worst_long_i64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_long_i64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand All @@ -43,7 +43,7 @@ fn minmax_i64_worst_case_array_short(c: &mut Criterion) {
let n = config::ARRAY_LENGTH_SHORT;
let data = utils::get_worst_case_array::<i64>(n, 1);
c.bench_function("simple_worst_short_i64", |b| {
b.iter(|| argminmax::generic::simple_argminmax(black_box(data.view())))
b.iter(|| argminmax::scalar_argminmax(black_box(data.view())))
});
c.bench_function("simd_worst_short_i64", |b| {
b.iter(|| black_box(data.view().argminmax()))
Expand Down
Loading

0 comments on commit f2d036f

Please # to comment.