Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

✨ support f16 + 🧹 some minor refactoring #1

Merged
merged 6 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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