mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 16:48:58 +00:00
394 lines
11 KiB
Rust
394 lines
11 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Lock optimization utilities.
|
|
//!
|
|
//! This module provides lock optimization strategies and statistics
|
|
//! to improve concurrent performance.
|
|
|
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
|
use std::time::{Duration, Instant};
|
|
|
|
/// Lock optimization configuration.
|
|
#[derive(Debug, Clone)]
|
|
pub struct LockOptimizeConfig {
|
|
/// Whether optimization is enabled.
|
|
pub enabled: bool,
|
|
/// Lock acquire timeout.
|
|
pub acquire_timeout: Duration,
|
|
/// Maximum hold time warning threshold.
|
|
pub max_hold_time_warning: Duration,
|
|
/// Enable adaptive spinning.
|
|
pub adaptive_spin: bool,
|
|
/// Maximum spin iterations.
|
|
pub max_spin_iterations: usize,
|
|
}
|
|
|
|
impl Default for LockOptimizeConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
acquire_timeout: Duration::from_secs(5),
|
|
max_hold_time_warning: Duration::from_millis(100),
|
|
adaptive_spin: true,
|
|
max_spin_iterations: 1000,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Lock statistics.
|
|
#[derive(Debug, Default)]
|
|
pub struct LockStats {
|
|
/// Number of locks acquired.
|
|
pub locks_acquired: AtomicU64,
|
|
/// Number of locks released early (before timeout).
|
|
pub locks_released_early: AtomicU64,
|
|
/// Total hold time in nanoseconds.
|
|
pub total_hold_time_ns: AtomicU64,
|
|
/// Maximum hold time in nanoseconds.
|
|
pub max_hold_time_ns: AtomicU64,
|
|
/// Number of contention events.
|
|
pub contentions: AtomicU64,
|
|
/// Number of spin successes.
|
|
pub spin_successes: AtomicU64,
|
|
/// Number of spin failures.
|
|
pub spin_failures: AtomicU64,
|
|
}
|
|
|
|
impl LockStats {
|
|
/// Create new lock statistics.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Record a lock acquisition.
|
|
pub fn record_acquire(&self) {
|
|
self.locks_acquired.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a lock release.
|
|
pub fn record_release(&self, hold_time: Duration) {
|
|
let ns = hold_time.as_nanos() as u64;
|
|
self.total_hold_time_ns.fetch_add(ns, Ordering::Relaxed);
|
|
|
|
// Update max hold time
|
|
let mut current = self.max_hold_time_ns.load(Ordering::Relaxed);
|
|
while ns > current {
|
|
match self
|
|
.max_hold_time_ns
|
|
.compare_exchange_weak(current, ns, Ordering::Relaxed, Ordering::Relaxed)
|
|
{
|
|
Ok(_) => break,
|
|
Err(actual) => current = actual,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Record an early release.
|
|
pub fn record_early_release(&self) {
|
|
self.locks_released_early.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a contention event.
|
|
pub fn record_contention(&self) {
|
|
self.contentions.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a spin success.
|
|
pub fn record_spin_success(&self) {
|
|
self.spin_successes.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a spin failure.
|
|
pub fn record_spin_failure(&self) {
|
|
self.spin_failures.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Get total locks acquired.
|
|
pub fn total_acquired(&self) -> u64 {
|
|
self.locks_acquired.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Get average hold time.
|
|
pub fn avg_hold_time(&self) -> Duration {
|
|
let total = self.total_hold_time_ns.load(Ordering::Relaxed);
|
|
let count = self.locks_acquired.load(Ordering::Relaxed);
|
|
total.checked_div(count).map(Duration::from_nanos).unwrap_or(Duration::ZERO)
|
|
}
|
|
|
|
/// Get maximum hold time.
|
|
pub fn max_hold_time(&self) -> Duration {
|
|
Duration::from_nanos(self.max_hold_time_ns.load(Ordering::Relaxed))
|
|
}
|
|
|
|
/// Get contention rate.
|
|
pub fn contention_rate(&self) -> f64 {
|
|
let acquired = self.locks_acquired.load(Ordering::Relaxed);
|
|
let contentions = self.contentions.load(Ordering::Relaxed);
|
|
if acquired == 0 {
|
|
0.0
|
|
} else {
|
|
contentions as f64 / acquired as f64
|
|
}
|
|
}
|
|
|
|
/// Get spin success rate.
|
|
pub fn spin_success_rate(&self) -> f64 {
|
|
let successes = self.spin_successes.load(Ordering::Relaxed);
|
|
let failures = self.spin_failures.load(Ordering::Relaxed);
|
|
let total = successes + failures;
|
|
if total == 0 { 0.0 } else { successes as f64 / total as f64 }
|
|
}
|
|
|
|
/// Reset all statistics.
|
|
pub fn reset(&self) {
|
|
self.locks_acquired.store(0, Ordering::Relaxed);
|
|
self.locks_released_early.store(0, Ordering::Relaxed);
|
|
self.total_hold_time_ns.store(0, Ordering::Relaxed);
|
|
self.max_hold_time_ns.store(0, Ordering::Relaxed);
|
|
self.contentions.store(0, Ordering::Relaxed);
|
|
self.spin_successes.store(0, Ordering::Relaxed);
|
|
self.spin_failures.store(0, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
/// Lock optimizer.
|
|
pub struct LockOptimizer {
|
|
/// Configuration.
|
|
config: LockOptimizeConfig,
|
|
/// Statistics.
|
|
stats: LockStats,
|
|
/// Current spin iterations (adaptive).
|
|
current_spin: AtomicUsize,
|
|
}
|
|
|
|
impl LockOptimizer {
|
|
/// Create a new lock optimizer.
|
|
pub fn new(config: LockOptimizeConfig) -> Self {
|
|
Self {
|
|
config,
|
|
stats: LockStats::new(),
|
|
current_spin: AtomicUsize::new(100),
|
|
}
|
|
}
|
|
|
|
/// Create with default configuration.
|
|
pub fn with_defaults() -> Self {
|
|
Self::new(LockOptimizeConfig::default())
|
|
}
|
|
|
|
/// Get the configuration.
|
|
pub fn config(&self) -> &LockOptimizeConfig {
|
|
&self.config
|
|
}
|
|
|
|
/// Get the statistics.
|
|
pub fn stats(&self) -> &LockStats {
|
|
&self.stats
|
|
}
|
|
|
|
/// Record lock acquisition.
|
|
pub fn on_acquire(&self) {
|
|
if !self.config.enabled {
|
|
return;
|
|
}
|
|
self.stats.record_acquire();
|
|
}
|
|
|
|
/// Record lock release.
|
|
pub fn on_release(&self, hold_time: Duration) {
|
|
if !self.config.enabled {
|
|
return;
|
|
}
|
|
self.stats.record_release(hold_time);
|
|
|
|
// Check for early release
|
|
if hold_time < self.config.acquire_timeout / 2 {
|
|
self.stats.record_early_release();
|
|
}
|
|
}
|
|
|
|
/// Record contention.
|
|
pub fn on_contention(&self) {
|
|
if !self.config.enabled {
|
|
return;
|
|
}
|
|
self.stats.record_contention();
|
|
}
|
|
|
|
/// Perform adaptive spin.
|
|
///
|
|
/// Returns true if the lock was acquired during spinning.
|
|
pub fn try_spin<F>(&self, mut try_acquire: F) -> bool
|
|
where
|
|
F: FnMut() -> bool,
|
|
{
|
|
if !self.config.enabled || !self.config.adaptive_spin {
|
|
return false;
|
|
}
|
|
|
|
let spin_count = self.current_spin.load(Ordering::Relaxed).min(self.config.max_spin_iterations);
|
|
|
|
for _ in 0..spin_count {
|
|
if try_acquire() {
|
|
self.stats.record_spin_success();
|
|
self.adapt_spin(true);
|
|
return true;
|
|
}
|
|
// Hint to the CPU that we're spinning
|
|
std::hint::spin_loop();
|
|
}
|
|
|
|
self.stats.record_spin_failure();
|
|
self.adapt_spin(false);
|
|
false
|
|
}
|
|
|
|
/// Adapt spin count based on success/failure.
|
|
fn adapt_spin(&self, success: bool) {
|
|
let current = self.current_spin.load(Ordering::Relaxed);
|
|
let new_count = if success {
|
|
// Increase spin count on success (up to max)
|
|
(current * 2).min(self.config.max_spin_iterations)
|
|
} else {
|
|
// Decrease spin count on failure (down to min)
|
|
(current / 2).max(10)
|
|
};
|
|
self.current_spin.store(new_count, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Get current spin count.
|
|
pub fn current_spin_count(&self) -> usize {
|
|
self.current_spin.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Check if hold time is excessive.
|
|
pub fn is_hold_time_excessive(&self, hold_time: Duration) -> bool {
|
|
hold_time > self.config.max_hold_time_warning
|
|
}
|
|
|
|
/// Reset statistics.
|
|
pub fn reset_stats(&self) {
|
|
self.stats.reset();
|
|
}
|
|
}
|
|
|
|
/// RAII guard for tracking lock hold time.
|
|
pub struct LockGuard<'a> {
|
|
optimizer: &'a LockOptimizer,
|
|
start: Instant,
|
|
}
|
|
|
|
impl<'a> LockGuard<'a> {
|
|
/// Create a new lock guard.
|
|
pub fn new(optimizer: &'a LockOptimizer) -> Self {
|
|
optimizer.on_acquire();
|
|
Self {
|
|
optimizer,
|
|
start: Instant::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for LockGuard<'_> {
|
|
fn drop(&mut self) {
|
|
self.optimizer.on_release(self.start.elapsed());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_lock_stats() {
|
|
let stats = LockStats::new();
|
|
|
|
stats.record_acquire();
|
|
stats.record_acquire();
|
|
stats.record_release(Duration::from_millis(10));
|
|
stats.record_release(Duration::from_millis(20));
|
|
|
|
assert_eq!(stats.total_acquired(), 2);
|
|
assert!(stats.avg_hold_time() >= Duration::from_millis(15));
|
|
assert_eq!(stats.max_hold_time(), Duration::from_millis(20));
|
|
}
|
|
|
|
#[test]
|
|
fn test_contention_rate() {
|
|
let stats = LockStats::new();
|
|
|
|
stats.record_acquire();
|
|
stats.record_acquire();
|
|
stats.record_acquire();
|
|
stats.record_contention();
|
|
|
|
assert!((stats.contention_rate() - 0.3333333333333333).abs() < 0.01);
|
|
}
|
|
|
|
#[test]
|
|
fn test_spin_stats() {
|
|
let stats = LockStats::new();
|
|
|
|
stats.record_spin_success();
|
|
stats.record_spin_success();
|
|
stats.record_spin_failure();
|
|
|
|
assert!((stats.spin_success_rate() - 0.6666666666666666).abs() < 0.01);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lock_optimizer() {
|
|
let optimizer = LockOptimizer::with_defaults();
|
|
|
|
{
|
|
let _guard = LockGuard::new(&optimizer);
|
|
std::thread::sleep(Duration::from_millis(10));
|
|
}
|
|
|
|
assert_eq!(optimizer.stats().total_acquired(), 1);
|
|
assert!(optimizer.stats().avg_hold_time() >= Duration::from_millis(10));
|
|
}
|
|
|
|
#[test]
|
|
fn test_adaptive_spin() {
|
|
let optimizer = LockOptimizer::with_defaults();
|
|
|
|
// Simulate successful spin
|
|
let acquired = optimizer.try_spin(|| true);
|
|
assert!(acquired);
|
|
assert!(optimizer.current_spin_count() > 100); // Should increase
|
|
|
|
// Simulate failed spin
|
|
let acquired = optimizer.try_spin(|| false);
|
|
assert!(!acquired);
|
|
assert!(optimizer.current_spin_count() < 200); // Should decrease
|
|
}
|
|
|
|
#[test]
|
|
fn test_disabled_optimizer() {
|
|
let config = LockOptimizeConfig {
|
|
enabled: false,
|
|
..Default::default()
|
|
};
|
|
let optimizer = LockOptimizer::new(config);
|
|
|
|
optimizer.on_acquire();
|
|
optimizer.on_release(Duration::from_millis(10));
|
|
|
|
// Should not track when disabled
|
|
assert_eq!(optimizer.stats().total_acquired(), 0);
|
|
}
|
|
}
|