Questo articolo spiega, in primo luogo, come eseguire il benchmark utilizzando il popolare criterio cassa. Quindi fornisce informazioni aggiuntive che mostrano come eseguire il benchmark tra le impostazioni del compilatore. Sebbene ogni combinazione di impostazioni del compilatore richieda la ricompilazione e un’esecuzione separata, possiamo comunque tabulare e analizzare i risultati. L’articolo è un complemento dell’articolo Nove regole per l’accelerazione SIMD del tuo codice Rust In Verso la scienza dei dati.
Applicheremo questa tecnica al file range-set-blaze
cassa. Il nostro obiettivo è misurare gli effetti sulle prestazioni di varie impostazioni SIMD (Single Instruction, Multiple Data). Vogliamo anche confrontare le prestazioni tra diverse CPU. Questo approccio è utile anche per comprendere i vantaggi dei diversi livelli di ottimizzazione.
Nel contesto di range-set-blaze
valutiamo:
- 3 livelli di estensione SIMD —
sse2
(128 bit),avx2
(256 bit),avx512f
(512 bit) - 10 tipi di elementi —
i8
,u8
,i16
,u16
,i32
,u32
,i64
,u64
,isize
,usize
- 5 numeri di corsia: 4, 8, 16, 32, 64
- 2 CPU: AMD 7950X con
avx512f
Intel i5–8250U conavx2
- 5 algoritmi: Regolare, Splat0, Splat1, Splat2, Ruota
- 4 lunghezze di ingresso — 1024; 10.240; 102.400; 1.024.000
Di questi, regoliamo esternamente le prime quattro variabili (livello di estensione SIMD, tipo di elemento, numero di corsia, CPU). Abbiamo controllato le ultime due variabili (algoritmo e lunghezza dell’input) con cicli all’interno del normale codice del benchmark Rust.
Per aggiungere benchmarking al tuo progetto, aggiungi questa dipendenza dev e crea una sottocartella:
cargo add criterion --dev --features html_reports
mkdir benches
In Cargo.toml
aggiungere:
((bench))
name = "bench"
harness = false
Creare un benches/bench.rs
. Ecco un esempio:
#!(feature(portable_simd))
#!(feature(array_chunks))
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use is_consecutive1::*;// create a string from the SIMD extension used
const SIMD_SUFFIX: &str = if cfg!(target_feature = "avx512f") {
"avx512f,512"
} else if cfg!(target_feature = "avx2") {
"avx2,256"
} else if cfg!(target_feature = "sse2") {
"sse2,128"
} else {
"error"
};
type Integer = i32;
const LANES: usize = 64;
// compare against this
#(inline)
pub fn is_consecutive_regular(chunk: &(Integer; LANES)) -> bool {
for i in 1..LANES {
if chunk(i - 1).checked_add(1) != Some(chunk(i)) {
return false;
}
}
true
}
// define a benchmark called "simple"
fn simple(c: &mut Criterion) {
let mut group = c.benchmark_group("simple");
group.sample_size(1000);
// generate about 1 million aligned elements
let parameter: Integer = 1_024_000;
let v = (100..parameter + 100).collect::<Vec<_>>();
let (prefix, simd_chunks, reminder) = v.as_simd::<LANES>(); // keep aligned part
let v = &v(prefix.len()..v.len() - reminder.len()); // keep aligned part
group.bench_function(format!("regular,{}", SIMD_SUFFIX), |b| {
b.iter(|| {
let _: usize = black_box(
v.array_chunks::<LANES>()
.map(|chunk| is_consecutive_regular(chunk) as usize)
.sum(),
);
});
});
group.bench_function(format!("splat1,{}", SIMD_SUFFIX), |b| {
b.iter(|| {
let _: usize = black_box(
simd_chunks
.iter()
.map(|chunk| IsConsecutive::is_consecutive(*chunk) as usize)
.sum(),
);
});
});
group.finish();
}
criterion_group!(benches, simple);
criterion_main!(benches);
Se vuoi eseguire questo esempio, il file il codice è su GitHub.
Esegui il benchmark con il comando cargo bench
. Verrà visualizzato un rapporto target/criterion/simple/report/index.html
e include grafici come questo che mostrano che Splat1 funziona molte volte più velocemente di Regular.
Abbiamo un problema. Vogliamo fare dei benchmark sse2
contro avx2
contro avx512f
che richiede (generalmente) compilazioni multiple e criterion
corre.
Ecco il nostro approccio:
- Utilizza uno script Bash per impostare variabili di ambiente e chiamare il benchmarking.
Per esempio,bench.sh
:
#!/bin/bash
SIMD_INTEGER_VALUES=("i64" "i32" "i16" "i8" "isize" "u64" "u32" "u16" "u8" "usize")
SIMD_LANES_VALUES=(64 32 16 8 4)
RUSTFLAGS_VALUES=("-C target-feature=+avx512f" "-C target-feature=+avx2" "")for simdLanes in "${SIMD_LANES_VALUES(@)}"; do
for simdInteger in "${SIMD_INTEGER_VALUES(@)}"; do
for rustFlags in "${RUSTFLAGS_VALUES(@)}"; do
echo "Running with SIMD_INTEGER=$simdInteger, SIMD_LANES=$simdLanes, RUSTFLAGS=$rustFlags"
SIMD_LANES=$simdLanes SIMD_INTEGER=$simdInteger RUSTFLAGS="$rustFlags" cargo bench
done
done
done
A parte: puoi usa facilmente Bash su Windows se hai Git e/o VS Code.
- Usare un
build.rs
per trasformare queste variabili d’ambiente in configurazioni Rust:
use std::env;fn main() {
if let Ok(simd_lanes) = env::var("SIMD_LANES") {
println!("cargo:rustc-cfg=simd_lanes=\"{}\"", simd_lanes);
println!("cargo:rerun-if-env-changed=SIMD_LANES");
}
if let Ok(simd_integer) = env::var("SIMD_INTEGER") {
println!("cargo:rustc-cfg=simd_integer=\"{}\"", simd_integer);
println!("cargo:rerun-if-env-changed=SIMD_INTEGER");
}
}
const SIMD_SUFFIX: &str = if cfg!(target_feature = "avx512f") {
"avx512f,512"
} else if cfg!(target_feature = "avx2") {
"avx2,256"
} else if cfg!(target_feature = "sse2") {
"sse2,128"
} else {
"error"
};#(cfg(simd_integer = "i8"))
type Integer = i8;
#(cfg(simd_integer = "i16"))
type Integer = i16;
#(cfg(simd_integer = "i32"))
type Integer = i32;
#(cfg(simd_integer = "i64"))
type Integer = i64;
#(cfg(simd_integer = "isize"))
type Integer = isize;
#(cfg(simd_integer = "u8"))
type Integer = u8;
#(cfg(simd_integer = "u16"))
type Integer = u16;
#(cfg(simd_integer = "u32"))
type Integer = u32;
#(cfg(simd_integer = "u64"))
type Integer = u64;
#(cfg(simd_integer = "usize"))
type Integer = usize;
#(cfg(not(any(
simd_integer = "i8",
simd_integer = "i16",
simd_integer = "i32",
simd_integer = "i64",
simd_integer = "isize",
simd_integer = "u8",
simd_integer = "u16",
simd_integer = "u32",
simd_integer = "u64",
simd_integer = "usize"
))))
type Integer = i32;
const LANES: usize = if cfg!(simd_lanes = "2") {
2
} else if cfg!(simd_lanes = "4") {
4
} else if cfg!(simd_lanes = "8") {
8
} else if cfg!(simd_lanes = "16") {
16
} else if cfg!(simd_lanes = "32") {
32
} else {
64
};
- In
benches.rs
crea un ID benchmark che registra la combinazione di variabili che stai testando, separate da virgole. Può essere una stringa o un criterioBenchmarkId
. Ho creato unBenchmarkId
con questo appello:create_benchmark_id::<Integer>("regular", LANES, *parameter)
a questa funzione:
fn create_benchmark_id<T>(name: &str, lanes: usize, parameter: usize) -> BenchmarkId
where
T: SimdElement,
{
BenchmarkId::new(
format!(
"{},{},{},{},{}",
name,
SIMD_SUFFIX,
type_name::<T>(),
mem::size_of::<T>() * 8,
lanes,
),
parameter,
)
}
Installare:
cargo install cargo-criterion-means
Correre:
cargo criterion-means > results.csv
Esempio di uscita:
Group,Id,Parameter,Mean(ns),StdErr(ns)
vector,regular,avx2,256,i16,16,16,1024,291.47,0.080141
vector,regular,avx2,256,i16,16,16,10240,2821.6,3.3949
vector,regular,avx2,256,i16,16,16,102400,28224,7.8341
vector,regular,avx2,256,i16,16,16,1024000,287220,67.067
# ...
Un file CSV è adatto per l’analisi tramite tabelle pivot dei fogli di calcolo o strumenti per frame di dati come polare.
Ad esempio, ecco la parte superiore del mio file di dati Excel lungo 5000 righe:
Le colonne da A a J provengono dal benchmark. Le colonne da K a N vengono calcolate da Excel.
Ecco una tabella pivot (e un grafico) basata sui dati. Mostra l’effetto della variazione del numero di corsie SIMD sul throughput. Il grafico calcola la media tra il tipo di elemento e la lunghezza dell’input. Il grafico suggerisce che per i migliori algoritmi, la soluzione migliore è 32 o 64 corsie.
Con questa analisi possiamo ora scegliere il nostro algoritmo e decidere come vogliamo impostare il parametro LANES.
Grazie per esserti unito a me in questo viaggio nel benchmarking Criterion.
Se non hai mai utilizzato Criterion prima, spero che questo ti incoraggi a provarlo. Se hai utilizzato Criterion ma non sei riuscito a misurare tutto ciò a cui tieni, spero che questo ti dia un percorso da seguire. Abbracciare Criterion in questo modo ampliato può sbloccare informazioni più approfondite sulle caratteristiche prestazionali dei tuoi progetti Rust.
Per favore segui Carl su Medium. Scrivo di programmazione scientifica in Rust e Python, machine learning e statistica. Tendo a scrivere circa un articolo al mese.
Fonte: towardsdatascience.com