10000 fix: remove support for AMD tsc counter by andylokandy · Pull Request #28 · tikv/minstant · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix: remove support for AMD tsc counter #28

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

Merged
merged 2 commits into from
Aug 28, 2023
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "minstant"
version = "0.1.2"
version = "0.1.3"
authors = ["The TiKV Authors"]
edition = "2018"
edition = "2021"
license = "MIT"
description = "A drop-in replacement for `std::time::Instant` that measures time with high performance and high accuracy powered by TSC"
homepage = "https://github.com/tikv/minstant"
Expand Down
227 changes: 7 additions & 220 deletions src/tsc_now.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

//! This module will be compiled when it's either linux_x86 or linux_x86_64.

use libc::{cpu_set_t, sched_setaffinity, CPU_SET};
use std::io::prelude::*;
use std::io::BufReader;
use std::mem::{size_of, zeroed, MaybeUninit};
use std::time::Instant;
use std::{cell::UnsafeCell, fs::read_to_string};

Expand All @@ -30,7 +26,6 @@ unsafe fn init() {
let tsc_level = TSCLevel::get();
let is_tsc_available = match &tsc_level {
TSCLevel::Stable { .. } => true,
TSCLevel::PerCPUStable { .. } => true,
TSCLevel::Unstable => false,
};
if is_tsc_available {
Expand All @@ -57,13 +52,6 @@ pub(crate) fn current_cycle() -> u64 {
TSCLevel::Stable {
cycles_from_anchor, ..
} => tsc().wrapping_sub(*cycles_from_anchor),
TSCLevel::PerCPUStable {
cycles_from_anchor, ..
} => {
let (tsc, cpuid) = tsc_with_cpuid();
let anchor = cycles_from_anchor[cpuid];
tsc.wrapping_sub(anchor)
}
TSCLevel::Unstable => panic!("tsc is unstable"),
}
}
Expand All @@ -73,93 +61,21 @@ enum TSCLevel {
cycles_per_second: u64,
cycles_from_anchor: u64,
},
PerCPUStable {
cycles_per_second: u64,
cycles_from_anchor: Vec<u64>,
},
Unstable,
}

impl TSCLevel {
fn get() -> TSCLevel {
let anchor = Instant::now();
if is_tsc_stable() {
let (cps, cfa) = cycles_per_sec(anchor);
return TSCLevel::Stable {
cycles_per_second: cps,
cycles_from_anchor: cfa,
};
if !is_tsc_stable() {
return TSCLevel::Unstable;
}

if is_tsc_percpu_stable() {
// Retrieve the IDs of all active CPUs.
let cpuids = if let Ok(cpuids) = available_cpus() {
if cpuids.is_empty() {
return TSCLevel::Unstable;
}

cpuids
} else {
return TSCLevel::Unstable;
};

let max_cpu_id = *cpuids.iter().max().unwrap();

// Spread the threads to all CPUs and calculate
// cycles from anchor separately
let handles = cpuids.into_iter().map(|id| {
std::thread::spawn(move || {
set_affinity(id).unwrap();

// check if cpu id matches IA32_TSC_AUX
let (_, cpuid) = tsc_with_cpuid();
assert_eq!(cpuid, id);

let (cps, cfa) = cycles_per_sec(anchor);

(id, cps, cfa)
})
});

// Block and wait for all threads finished
let results = handles.map(|h| h.join()).collect::<Result<Vec<_>, _>>();

let results = if let Ok(results) = results {
results
} else {
return TSCLevel::Unstable;
};

// Indexed by CPU ID
let mut cycles_from_anchor = vec![0; max_cpu_id + 1];

// Rates of TSCs on different CPUs won't be a big gap
// or it's unstable.
let mut max_cps = std::u64::MIN;
let mut min_cps = std::u64::MAX;
let mut sum_cps = 0;
let len = results.len();
for (cpuid, cps, cfa) in results {
if cps > max_cps {
max_cps = cps;
}
if cps < min_cps {
min_cps = cps;
}
sum_cps += cps;
cycles_from_anchor[cpuid] = cfa;
}
if (max_cps - min_cps) as f64 / min_cps as f64 > 0.0005 {
return TSCLevel::Unstable;
}

return TSCLevel::PerCPUStable {
cycles_per_second: sum_cps / len as u64,
cycles_from_anchor,
};
let anchor = Instant::now();
let (cps, cfa) = cycles_per_sec(anchor);
TSCLevel::Stable {
cycles_per_second: cps,
cycles_from_anchor: cfa,
}

TSCLevel::Unstable
}

#[inline]
Expand All @@ -168,9 +84,6 @@ impl TSCLevel {
TSCLevel::Stable {
cycles_per_second, ..
} => *cycles_per_second,
TSCLevel::PerCPUStable {
cycles_per_second, ..
} => *cycles_per_second,
TSCLevel::Unstable => panic!("tsc is unstable"),
}
}
Expand All @@ -186,36 +99,6 @@ fn is_tsc_stable() -> bool {
clock_source.map(|s| s.contains("tsc")).unwrap_or(false)
}

/// Checks if CPU flag contains `constant_tsc`, `nonstop_tsc` and
/// `rdtscp`. With these features, TSCs can be synced via offsets
/// between them and CPUID extracted from `IA32_TSC_AUX`.
fn is_tsc_percpu_stable() -> bool {
let f = || {
let cpuinfo = std::fs::File::open("/proc/cpuinfo").ok()?;
let mut cpuinfo = BufReader::new(cpuinfo);

let mut buf = String::with_capacity(1024);
loop {
if cpuinfo.read_line(&mut buf).ok()? == 0 {
break;
}

if buf.starts_with("flags") {
break;
}

buf.clear();
}

let constant_tsc = buf.contains("constant_tsc");
let nonstop_tsc = buf.contains("nonstop_tsc");
let rdtscp = buf.contains("rdtscp");
Some(constant_tsc && nonstop_tsc && rdtscp)
};

f().unwrap_or(false)
}

/// Returns (1) cycles per second and (2) cycles from anchor.
/// The result of subtracting `cycles_from_anchor` from newly fetched TSC
/// can be used to
Expand Down Expand Up @@ -275,99 +158,3 @@ fn tsc() -> u64 {

unsafe { _rdtsc() }
}

#[inline]
fn tsc_with_cpuid() -> (u64, usize) {
#[cfg(target_arch = "x86")]
use core::arch::x86::__rdtscp;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::__rdtscp;

let mut aux = MaybeUninit::<u32>::uninit();
let tsc = unsafe { __rdtscp(aux.as_mut_ptr()) };
let aux = unsafe { aux.assume_init() };

// IA32_TSC_AUX are encoded by Linux kernel as follow format:
//
// 31 12 11 0
// [ node id ][ cpu id ]
//
// See: https://elixir.bootlin.com/linux/v5.7.7/source/arch/x86/include/asm/segment.h#L249

// extract cpu id and check the same
(tsc, (aux & 0xfff) as usize)
}

// Retrieve available CPUs from `/sys` filesystem.
fn available_cpus() -> Result<Vec<usize>, Error> {
let s = read_to_string("/sys/devices/system/cpu/online")?;
parse_cpu_list_format(&s)
}

/// A wrapper function of sched_setaffinity(2)
fn set_affinity(cpuid: usize) -> Result<(), Error> {
let mut set = unsafe { zeroed::<cpu_set_t>() };

unsafe { CPU_SET(cpuid, &mut set) };

// Set the current thread's core affinity.
if unsafe {
sched_setaffinity(
0, // Defaults to current thread
size_of::<cpu_set_t>(),
&set as *const _,
)
} != 0
{
Err(std::io::Error::last_os_error().into())
} else {
Ok(())
}
}

/// List format
/// The List Format for cpus and mems is a comma-separated list of CPU or
/// memory-node numbers and ranges of numbers, in ASCII decimal.
///
/// Examples of the List Format:
/// 0-4,9 # bits 0, 1, 2, 3, 4, and 9 set
/// 0-2,7,12-14 # bits 0, 1, 2, 7, 12, 13, and 14 set
fn parse_cpu_list_format(list: &str) -> Result<Vec<usize>, Error> {
let mut res = vec![];
let list = list.trim();
for set in list.split(',') {
if set.contains('-') {
let mut ft = set.splitn(2, '-');
let from = ft.next().ok_or("expected from")?.parse::<usize>()?;
let to = ft.next().ok_or("expected to")?.parse::<usize>()?;
for i in from..=to {
res.push(i);
}
} else {
res.push(set.parse()?)
}
}

Ok(res)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_list_format() {
assert_eq!(
parse_cpu_list_format("0-2,7,12-14\n").unwrap(),
&[0, 1, 2, 7, 12, 13, 14]
);
assert_eq!(
parse_cpu_list_format("0-4,9\n").unwrap(),
&[0, 1, 2, 3, 4, 9]
);
assert_eq!(
parse_cpu_list_format("0-15\n").unwrap(),
(0..=15).collect::<Vec<_>>()
);
}
}
0