mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-10-28 08:08:58 +00:00
Replace statistical with local floating-point helpers
This commit is contained in:
1 parent
6cb9566118
commit
1fb03c3807
7 files changed
+135
-252
No files matched your search
Generated
+3
-247
@@ -97,27 +97,12 @@ dependencies = [
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
|
||||
dependencies = [
|
||||
"autocfg 1.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
@@ -231,15 +216,6 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
@@ -352,12 +328,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -421,7 +391,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shell-words",
|
||||
"statistical",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -508,7 +477,7 @@ version = "0.31.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -520,79 +489,13 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg 1.5.1",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
|
||||
dependencies = [
|
||||
"autocfg 1.5.1",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg 1.5.1",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg 1.5.1",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -697,25 +600,6 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"libc",
|
||||
"rand_chacha 0.1.1",
|
||||
"rand_core 0.4.3",
|
||||
"rand_hc",
|
||||
"rand_isaac",
|
||||
"rand_jitter",
|
||||
"rand_os",
|
||||
"rand_pcg",
|
||||
"rand_xorshift",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.8"
|
||||
@@ -748,16 +632,6 @@ dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"rand_core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
@@ -778,21 +652,6 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96f815e01bbd9678b50d927f79aa1cf3ffdfdb1b9787317c1284dadb894ad0e8"
|
||||
dependencies = [
|
||||
"rand_core 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e5937858e6fd18cd595d558f90bb5de3b72ae23f9e3763af0e805949b04ef60"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
@@ -817,77 +676,6 @@ version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
dependencies = [
|
||||
"rand_core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
dependencies = [
|
||||
"rand_core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_jitter"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_core 0.4.3",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
dependencies = [
|
||||
"cloudabi",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.4.3",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"rand_core 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||
dependencies = [
|
||||
"rand_core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
@@ -940,7 +728,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -1014,16 +802,6 @@ version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "statistical"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49d57902bb128e5e38b5218d3681215ae3e322d99f65d5420e9849730d2ea372"
|
||||
dependencies = [
|
||||
"num",
|
||||
"rand 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -1225,28 +1003,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -20,7 +20,6 @@ windows_process_extensions_main_thread_handle = []
|
||||
colored = "3.1"
|
||||
# Newer versions regress ETA estimates: https://github.com/console-rs/indicatif/issues/580
|
||||
indicatif = "=0.17.4"
|
||||
statistical = "1.0"
|
||||
csv = "1.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -9,12 +9,12 @@ use crate::options::{
|
||||
use crate::output::progress_bar::get_progress_bar;
|
||||
use crate::timer::execute_and_measure;
|
||||
use crate::util::randomized_environment_offset;
|
||||
use crate::util::statistics::mean;
|
||||
use crate::util::units::Second;
|
||||
|
||||
use super::measurement::Measurement;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use statistical::mean;
|
||||
|
||||
pub enum BenchmarkIteration {
|
||||
NonBenchmarkRun,
|
||||
|
||||
@@ -20,13 +20,13 @@ use crate::output::warnings::{OutlierWarningOptions, Warnings};
|
||||
use crate::parameter::ParameterNameAndValue;
|
||||
use crate::util::exit_code::extract_exit_code;
|
||||
use crate::util::min_max::{max, min};
|
||||
use crate::util::statistics::{mean, median, standard_deviation};
|
||||
use crate::util::units::Second;
|
||||
use benchmark_result::BenchmarkResult;
|
||||
use measurement::Measurement;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::*;
|
||||
use statistical::{mean, median, standard_deviation};
|
||||
|
||||
use self::executor::Executor;
|
||||
|
||||
@@ -339,7 +339,7 @@ impl<'a> Benchmark<'a> {
|
||||
let t_num = times_real.len();
|
||||
let t_mean = mean(×_real);
|
||||
let t_stddev = if times_real.len() > 1 {
|
||||
Some(standard_deviation(×_real, Some(t_mean)))
|
||||
Some(standard_deviation(×_real, t_mean))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! The ASQC Basic References in Quality Control: Statistical Techniques, Edward F. Mykytka,
|
||||
//! Ph.D., Editor.
|
||||
|
||||
use statistical::median;
|
||||
use crate::util::statistics::median;
|
||||
|
||||
/// Minimum modified Z-score for a datapoint to be an outlier. Here, 1.4826 is a factor that
|
||||
/// converts the MAD to an estimator for the standard deviation. The second factor is the number
|
||||
|
||||
@@ -2,4 +2,5 @@ pub mod exit_code;
|
||||
pub mod min_max;
|
||||
pub mod number;
|
||||
pub mod randomized_environment_offset;
|
||||
pub mod statistics;
|
||||
pub mod units;
|
||||
@@ -0,0 +1,127 @@
|
||||
//! Statistics for non-empty samples of finite measurements.
|
||||
|
||||
/// Arithmetic mean, summing measurements in their original order.
|
||||
pub fn mean(values: &[f64]) -> f64 {
|
||||
values.iter().fold(0.0, |sum, value| sum + value) / values.len() as f64
|
||||
}
|
||||
|
||||
/// Median without modifying the input sample.
|
||||
pub fn median(values: &[f64]) -> f64 {
|
||||
assert!(
|
||||
!values.is_empty(),
|
||||
"median requires at least one measurement"
|
||||
);
|
||||
let mut sorted = values.to_vec();
|
||||
sorted.sort_unstable_by(|a, b| a.partial_cmp(b).expect("No NaN measurements"));
|
||||
|
||||
let mid = sorted.len() / 2;
|
||||
if sorted.len() % 2 == 1 {
|
||||
sorted[mid]
|
||||
} else {
|
||||
(sorted[mid] + sorted[mid - 1]) / 2.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample standard deviation, using an already computed mean.
|
||||
pub fn standard_deviation(values: &[f64], mean: f64) -> f64 {
|
||||
assert!(
|
||||
values.len() > 1,
|
||||
"standard deviation requires at least two measurements"
|
||||
);
|
||||
let sum = values
|
||||
.iter()
|
||||
.map(|value| (value - mean) * (value - mean))
|
||||
.fold(0.0, |sum, deviation| sum + deviation);
|
||||
assert!(sum >= 0.0, "invalid sum of squared deviations");
|
||||
|
||||
// Preserve the operation order of statistical 1.0: dividing by n - 1
|
||||
// before taking the square root also preserves exported floating-point values.
|
||||
(sum / (values.len() as f64 - 1.0)).sqrt()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mean_preserves_summation_order() {
|
||||
assert_eq!(mean(&[1.0]), 1.0);
|
||||
assert_eq!(mean(&[1.0, 2.0, 6.0]), 3.0);
|
||||
assert_eq!(mean(&[1e16, 1.0, -1e16]), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn median_handles_odd_even_and_repeated_values() {
|
||||
assert_eq!(median(&[7.0]), 7.0);
|
||||
assert_eq!(median(&[6.0, 1.0, 2.0]), 2.0);
|
||||
assert_eq!(median(&[6.0, 1.0, 3.0, 2.0]), 2.5);
|
||||
assert_eq!(median(&[0.0, 0.0, 0.0, 1.0]), 0.0);
|
||||
assert_eq!(median(&[-3.0, -1.0, -2.0]), -2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn median_preserves_input_order() {
|
||||
let values = [3.0, 1.0, 2.0];
|
||||
assert_eq!(median(&values), 2.0);
|
||||
assert_eq!(values, [3.0, 1.0, 2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "median requires at least one measurement")]
|
||||
fn median_rejects_empty_samples() {
|
||||
median(&[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standard_deviation_uses_sample_variance() {
|
||||
assert_eq!(standard_deviation(&[1.0, 2.0, 3.0], 2.0), 1.0);
|
||||
assert_eq!(standard_deviation(&[0.0, 0.0], 0.0), 0.0);
|
||||
assert_eq!(standard_deviation(&[7.0, 7.0, 7.0], 7.0), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_statistical_1_0_floating_point_results() {
|
||||
// Recorded from statistical 1.0.0, including small durations and
|
||||
// small differences between large values. Compare bits to catch
|
||||
// changes that would affect the full precision JSON/CSV exports.
|
||||
let cases: &[(&[f64], u64, u64, u64)] = &[
|
||||
(
|
||||
&[0.09, 0.10, 0.14],
|
||||
0x3fbc28f5c28f5c29,
|
||||
0x3fb999999999999a,
|
||||
0x3f9b17ada62cc3bd,
|
||||
),
|
||||
(
|
||||
&[0.1, 0.2, 0.3, 0.4],
|
||||
0x3fd0000000000000,
|
||||
0x3fd0000000000000,
|
||||
0x3fc08654a2d4f6da,
|
||||
),
|
||||
(
|
||||
&[1e-9, 2e-9, 3e-9, 9e-9],
|
||||
0x3e301b2b29a4692c,
|
||||
0x3e25798ee2308c3a,
|
||||
0x3e2edf3cdee63e61,
|
||||
),
|
||||
(
|
||||
&[1e12 + 0.001, 1e12 + 0.002, 1e12 + 0.003],
|
||||
0x426d1a94a2000010,
|
||||
0x426d1a94a2000010,
|
||||
0x3f510785dd689a29,
|
||||
),
|
||||
];
|
||||
|
||||
for &(values, mean_bits, median_bits, stddev_bits) in cases {
|
||||
let mean = mean(values);
|
||||
assert_eq!(mean.to_bits(), mean_bits);
|
||||
assert_eq!(median(values).to_bits(), median_bits);
|
||||
assert_eq!(standard_deviation(values, mean).to_bits(), stddev_bits);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "standard deviation requires at least two measurements")]
|
||||
fn standard_deviation_rejects_single_measurements() {
|
||||
standard_deviation(&[1.0], 1.0);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user