Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add benchmark for the number of minimum cpu cores #5127

Merged
merged 15 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 15 additions & 5 deletions polkadot/node/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ pub fn new_full<
use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD;
use polkadot_node_network_protocol::request_response::IncomingRequest;
use sc_network_sync::WarpSyncParams;
use sc_sysinfo::Metric;

let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled;
let role = config.role.clone();
Expand Down Expand Up @@ -1082,11 +1083,20 @@ pub fn new_full<
sc_sysinfo::print_hwbench(&hwbench);
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) {
Err(err) if role.is_authority() => {
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
if err.0.iter().any(|failure| failure.metric == Metric::Blake2256Parallel) {
log::info!(
"⚠️ The hardware will fail the minimal parallel performance requirements {} for role 'Authority', find out more when this will become mandatory at:\n\
alexggh marked this conversation as resolved.
Show resolved Hide resolved
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
}
if err.0.iter().any(|failure| failure.metric != Metric::Blake2256Parallel) {
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
alexggh marked this conversation as resolved.
Show resolved Hide resolved
err
);
}
},
_ => {},
}
Expand Down
14 changes: 10 additions & 4 deletions substrate/client/sysinfo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ mod sysinfo;
mod sysinfo_linux;

pub use sysinfo::{
benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes,
benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo,
serialize_throughput, serialize_throughput_option, Metric, Requirement, Requirements,
Throughput,
benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes,
benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, gather_hwbench,
gather_sysinfo, serialize_throughput, serialize_throughput_option, Metric, Requirement,
Requirements, Throughput,
};

/// The operating system part of the current target triplet.
Expand All @@ -48,6 +48,10 @@ pub struct HwBench {
/// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash.
#[serde(serialize_with = "serialize_throughput")]
pub cpu_hashrate_score: Throughput,
/// The parallel CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256
/// hash, when using `EXPECTED_NUM_CORES` threads.
#[serde(serialize_with = "serialize_throughput")]
pub parallel_cpu_hashrate_score: Throughput,
/// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`.
#[serde(serialize_with = "serialize_throughput")]
pub memory_memcpy_score: Throughput,
Expand All @@ -65,6 +69,7 @@ pub struct HwBench {
pub disk_random_write_score: Option<Throughput>,
}

#[derive(Copy, Clone, Debug)]
/// Limit the execution time of a benchmark.
pub enum ExecutionLimit {
/// Limit by the maximal duration.
Expand Down Expand Up @@ -133,6 +138,7 @@ pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) {
/// Prints out the results of the hardware benchmarks in the logs.
pub fn print_hwbench(hwbench: &HwBench) {
log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score);
log::info!("🏁 CPU parallelism score: {}", hwbench.parallel_cpu_hashrate_score);
alexggh marked this conversation as resolved.
Show resolved Hide resolved
log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score);

if let Some(score) = hwbench.disk_sequential_write_score {
Expand Down
72 changes: 68 additions & 4 deletions substrate/client/sysinfo/src/sysinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ use sc_telemetry::SysInfo;
use sp_core::{sr25519, Pair};
use sp_io::crypto::sr25519_verify;

use core::f64;
use derive_more::From;
use rand::{seq::SliceRandom, Rng, RngCore};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{
fmt,
fmt::{Display, Formatter},
fmt::{self, Display, Formatter},
fs::File,
io::{Seek, SeekFrom, Write},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
sync::{Arc, Barrier},
time::{Duration, Instant},
};

Expand All @@ -42,6 +43,8 @@ pub enum Metric {
Sr25519Verify,
/// Blake2-256 hashing algorithm.
Blake2256,
/// Blake2-256 hashing algorithm executed in parallel.
Blake2256Parallel,
/// Copying data in RAM.
MemCopy,
/// Disk sequential write.
Expand Down Expand Up @@ -85,7 +88,7 @@ impl Metric {
/// The category of the metric.
pub fn category(&self) -> &'static str {
match self {
Self::Sr25519Verify | Self::Blake2256 => "CPU",
Self::Sr25519Verify | Self::Blake2256 | Self::Blake2256Parallel => "CPU",
Self::MemCopy => "Memory",
Self::DiskSeqWrite | Self::DiskRndWrite => "Disk",
}
Expand All @@ -96,6 +99,7 @@ impl Metric {
match self {
Self::Sr25519Verify => "SR25519-Verify",
Self::Blake2256 => "BLAKE2-256",
Self::Blake2256Parallel => "BLAKE2-256-Parallel",
Self::MemCopy => "Copy",
Self::DiskSeqWrite => "Seq Write",
Self::DiskRndWrite => "Rnd Write",
Expand Down Expand Up @@ -375,6 +379,49 @@ pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput {
.expect("benchmark cannot fail; qed")
}

// This benchmarks the entire CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per
// second. It spawns multiple threads to measure the throughput of the entire CPU and averages the
// score obtained by each thread. If we have at least `EXPECTED_NUM_CORES` available then the
// average throughput should be relatively close to the single core performance as measured by
// `benchmark_cpu`.
pub fn benchmark_cpu_parallelism(limit: ExecutionLimit) -> Throughput {
const SIZE: usize = 32 * 1024;
const EXPECTED_NUM_CORES: usize = 8;
alexggh marked this conversation as resolved.
Show resolved Hide resolved

let ready_to_run_benchmark = Arc::new(Barrier::new(EXPECTED_NUM_CORES));
let mut benchmark_threads = Vec::new();

// Spawn a thread for each expected core and average the throughput for each of them.
for _ in 0..EXPECTED_NUM_CORES {
let ready_to_run_benchmark = ready_to_run_benchmark.clone();

let handle = std::thread::spawn(move || {
koute marked this conversation as resolved.
Show resolved Hide resolved
let mut buffer = Vec::new();
buffer.resize(SIZE, 0x66);
let mut hash = Default::default();

let run = || -> Result<(), ()> {
alexggh marked this conversation as resolved.
Show resolved Hide resolved
clobber_slice(&mut buffer);
hash = sp_crypto_hashing::blake2_256(&buffer);
clobber_slice(&mut hash);

Ok(())
};
ready_to_run_benchmark.wait();
benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run)
.expect("benchmark cannot fail; qed")
});
benchmark_threads.push(handle);
alexggh marked this conversation as resolved.
Show resolved Hide resolved
}

let average_score = benchmark_threads
.into_iter()
.map(|thread| thread.join().map(|throughput| throughput.as_kibs()).unwrap_or(f64::MIN))
alexggh marked this conversation as resolved.
Show resolved Hide resolved
.sum::<f64>() /
EXPECTED_NUM_CORES as f64;
Throughput::from_kibs(average_score)
}

/// A default [`ExecutionLimit`] that can be used to call [`benchmark_memory`].
pub const DEFAULT_MEMORY_EXECUTION_LIMIT: ExecutionLimit =
ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(100) };
Expand Down Expand Up @@ -628,6 +675,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench {
#[allow(unused_mut)]
let mut hwbench = HwBench {
cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT),
parallel_cpu_hashrate_score: benchmark_cpu_parallelism(DEFAULT_CPU_EXECUTION_LIMIT),
memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT),
disk_sequential_write_score: None,
disk_random_write_score: None,
Expand Down Expand Up @@ -671,6 +719,14 @@ impl Requirements {
found: hwbench.cpu_hashrate_score,
});
},
Metric::Blake2256Parallel =>
if requirement.minimum > hwbench.parallel_cpu_hashrate_score {
failures.push(CheckFailure {
metric: requirement.metric,
expected: requirement.minimum,
found: hwbench.parallel_cpu_hashrate_score,
});
},
Metric::MemCopy =>
if requirement.minimum > hwbench.memory_memcpy_score {
failures.push(CheckFailure {
Expand Down Expand Up @@ -732,6 +788,13 @@ mod tests {
assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0));
}

#[test]
fn test_benchmark_parallel_cpu() {
assert!(
benchmark_cpu_parallelism(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)
alexggh marked this conversation as resolved.
Show resolved Hide resolved
);
}

#[test]
fn test_benchmark_memory() {
assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > Throughput::from_mibs(0.0));
Expand Down Expand Up @@ -781,13 +844,14 @@ mod tests {
fn hwbench_serialize_works() {
let hwbench = HwBench {
cpu_hashrate_score: Throughput::from_gibs(1.32),
parallel_cpu_hashrate_score: Throughput::from_gibs(1.32),
memory_memcpy_score: Throughput::from_kibs(9342.432),
disk_sequential_write_score: Some(Throughput::from_kibs(4332.12)),
disk_random_write_score: None,
};

let serialized = serde_json::to_string(&hwbench).unwrap();
// Throughput from all of the benchmarks should be converted to MiBs.
assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}");
assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"parallel_cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}");
}
}
7 changes: 4 additions & 3 deletions substrate/utils/frame/benchmarking-cli/src/machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ use log::{error, info, warn};
use sc_cli::{CliConfiguration, Result, SharedParams};
use sc_service::Configuration;
use sc_sysinfo::{
benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes,
benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, Metric, Requirement, Requirements,
Throughput,
benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes,
benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, ExecutionLimit,
Metric, Requirement, Requirements, Throughput,
};

use crate::shared::check_build_profile;
Expand Down Expand Up @@ -150,6 +150,7 @@ impl MachineCmd {

let score = match metric {
Metric::Blake2256 => benchmark_cpu(hash_limit),
Metric::Blake2256Parallel => benchmark_cpu_parallelism(hash_limit),
Metric::Sr25519Verify => benchmark_sr25519_verify(verify_limit),
Metric::MemCopy => benchmark_memory(memory_limit),
Metric::DiskSeqWrite => benchmark_disk_sequential_writes(disk_limit, dir)?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"metric": "Blake2256",
"minimum": 783.27
},
{
"metric": "Blake2256Parallel",
"minimum": 783.27
},
{
"metric": "Sr25519Verify",
"minimum": 0.547529297
Expand Down
Loading