Files
rustfs/crates/lock/src/fast_lock/benchmarks.rs
T
guojidan d4beb1cc0b Fix lock (#510)
* Refactor: reimplement lock

Signed-off-by: junxiang Mu <1948535941@qq.com>

* Fix: fix test case failed

Signed-off-by: junxiang Mu <1948535941@qq.com>

* Improve: lock pref

Signed-off-by: junxiang Mu <1948535941@qq.com>

* fix(lock): Fix resource cleanup issue when batch lock acquisition fails
Ensure that the locks already acquired are properly released when batch lock acquisition fails to avoid memory leaks
Improve the lock protection mechanism to prevent double release issues
Add complete Apache license declarations to all files

Signed-off-by: junxiang Mu <1948535941@qq.com>

---------

Signed-off-by: junxiang Mu <1948535941@qq.com>
2025-09-11 12:10:35 +08:00

325 lines
13 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.
// Benchmarks comparing fast lock vs old lock performance
#[cfg(test)]
#[allow(dead_code)] // Temporarily disable benchmark tests
mod benchmarks {
use super::super::*;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::task;
/// Benchmark single-threaded lock operations
#[tokio::test]
async fn bench_single_threaded_fast_locks() {
let manager = Arc::new(FastObjectLockManager::new());
let iterations = 10000;
// Warm up
for i in 0..100 {
let _guard = manager
.acquire_write_lock("bucket", &format!("warm_{}", i), "owner")
.await
.unwrap();
}
// Benchmark write locks
let start = Instant::now();
for i in 0..iterations {
let _guard = manager
.acquire_write_lock("bucket", &format!("object_{}", i), "owner")
.await
.unwrap();
}
let duration = start.elapsed();
println!("Fast locks: {} write locks in {:?}", iterations, duration);
println!("Average: {:?} per lock", duration / iterations);
let metrics = manager.get_metrics();
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
// Should be much faster than old implementation
assert!(duration.as_millis() < 1000, "Should complete 10k locks in <1s");
assert!(metrics.shard_metrics.fast_path_rate() > 0.95, "Should have >95% fast path rate");
}
/// Benchmark concurrent lock operations
#[tokio::test]
async fn bench_concurrent_fast_locks() {
let manager = Arc::new(FastObjectLockManager::new());
let concurrent_tasks = 100;
let iterations_per_task = 100;
let start = Instant::now();
let mut handles = Vec::new();
for task_id in 0..concurrent_tasks {
let manager_clone = manager.clone();
let handle = task::spawn(async move {
for i in 0..iterations_per_task {
let object_name = format!("obj_{}_{}", task_id, i);
let _guard = manager_clone
.acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id))
.await
.unwrap();
// Simulate some work
tokio::task::yield_now().await;
}
});
handles.push(handle);
}
// Wait for all tasks
for handle in handles {
handle.await.unwrap();
}
let duration = start.elapsed();
let total_ops = concurrent_tasks * iterations_per_task;
println!("Concurrent fast locks: {} operations across {} tasks in {:?}",
total_ops, concurrent_tasks, duration);
println!("Throughput: {:.2} ops/sec", total_ops as f64 / duration.as_secs_f64());
let metrics = manager.get_metrics();
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
println!("Contention events: {}", metrics.shard_metrics.contention_events);
// Should maintain high throughput even with concurrency
assert!(duration.as_millis() < 5000, "Should complete concurrent ops in <5s");
}
/// Benchmark contended lock operations
#[tokio::test]
async fn bench_contended_locks() {
let manager = Arc::new(FastObjectLockManager::new());
let concurrent_tasks = 50;
let shared_objects = 10; // High contention on few objects
let iterations_per_task = 50;
let start = Instant::now();
let mut handles = Vec::new();
for task_id in 0..concurrent_tasks {
let manager_clone = manager.clone();
let handle = task::spawn(async move {
for i in 0..iterations_per_task {
let object_name = format!("shared_{}", i % shared_objects);
// Mix of read and write operations
if i % 3 == 0 {
// Write operation
if let Ok(_guard) = manager_clone
.acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id))
.await
{
tokio::task::yield_now().await;
}
} else {
// Read operation
if let Ok(_guard) = manager_clone
.acquire_read_lock("bucket", &object_name, &format!("owner_{}", task_id))
.await
{
tokio::task::yield_now().await;
}
}
}
});
handles.push(handle);
}
// Wait for all tasks
for handle in handles {
handle.await.unwrap();
}
let duration = start.elapsed();
println!("Contended locks: {} tasks on {} objects in {:?}",
concurrent_tasks, shared_objects, duration);
let metrics = manager.get_metrics();
println!("Total acquisitions: {}", metrics.shard_metrics.total_acquisitions());
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
println!("Average wait time: {:?}", metrics.shard_metrics.avg_wait_time());
println!("Timeout rate: {:.2}%", metrics.shard_metrics.timeout_rate() * 100.0);
// Even with contention, should maintain reasonable performance
assert!(metrics.shard_metrics.timeout_rate() < 0.1, "Should have <10% timeout rate");
assert!(metrics.shard_metrics.avg_wait_time() < Duration::from_millis(100), "Avg wait should be <100ms");
}
/// Benchmark batch operations
#[tokio::test]
async fn bench_batch_operations() {
let manager = FastObjectLockManager::new();
let batch_sizes = vec![10, 50, 100, 500];
for batch_size in batch_sizes {
// Create batch request
let mut batch = BatchLockRequest::new("batch_owner");
for i in 0..batch_size {
batch = batch.add_write_lock("bucket", &format!("batch_obj_{}", i));
}
let start = Instant::now();
let result = manager.acquire_locks_batch(batch).await;
let duration = start.elapsed();
assert!(result.all_acquired, "Batch should succeed");
println!("Batch size {}: {:?} ({:.2} μs per lock)",
batch_size,
duration,
duration.as_micros() as f64 / batch_size as f64);
// Batch should be much faster than individual acquisitions
assert!(duration.as_millis() < batch_size as u128 / 10,
"Batch should be 10x+ faster than individual locks");
}
}
/// Benchmark version-specific locks
#[tokio::test]
async fn bench_versioned_locks() {
let manager = Arc::new(FastObjectLockManager::new());
let objects = 100;
let versions_per_object = 10;
let start = Instant::now();
let mut handles = Vec::new();
for obj_id in 0..objects {
let manager_clone = manager.clone();
let handle = task::spawn(async move {
for version in 0..versions_per_object {
let _guard = manager_clone
.acquire_write_lock_versioned(
"bucket",
&format!("obj_{}", obj_id),
&format!("v{}", version),
"version_owner"
)
.await
.unwrap();
}
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
let duration = start.elapsed();
let total_ops = objects * versions_per_object;
println!("Versioned locks: {} version locks in {:?}", total_ops, duration);
println!("Throughput: {:.2} locks/sec", total_ops as f64 / duration.as_secs_f64());
let metrics = manager.get_metrics();
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
// Versioned locks should not interfere with each other
assert!(metrics.shard_metrics.fast_path_rate() > 0.9, "Should maintain high fast path rate");
}
/// Compare with theoretical maximum performance
#[tokio::test]
async fn bench_theoretical_maximum() {
let manager = Arc::new(FastObjectLockManager::new());
let iterations = 100000;
// Measure pure fast path performance (no contention)
let start = Instant::now();
for i in 0..iterations {
let _guard = manager
.acquire_write_lock("bucket", &format!("unique_{}", i), "owner")
.await
.unwrap();
}
let duration = start.elapsed();
println!("Theoretical maximum: {} unique locks in {:?}", iterations, duration);
println!("Rate: {:.2} locks/sec", iterations as f64 / duration.as_secs_f64());
println!("Latency: {:?} per lock", duration / iterations);
let metrics = manager.get_metrics();
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
// Should achieve very high performance with no contention
assert!(metrics.shard_metrics.fast_path_rate() > 0.99, "Should be nearly 100% fast path");
assert!(duration.as_secs_f64() / (iterations as f64) < 0.0001, "Should be <100μs per lock");
}
/// Performance regression test
#[tokio::test]
async fn performance_regression_test() {
let manager = Arc::new(FastObjectLockManager::new());
// This test ensures we maintain performance targets
let test_cases = vec![
("single_thread", 1, 10000),
("low_contention", 10, 1000),
("high_contention", 100, 100),
];
for (test_name, threads, ops_per_thread) in test_cases {
let start = Instant::now();
let mut handles = Vec::new();
for thread_id in 0..threads {
let manager_clone = manager.clone();
let handle = task::spawn(async move {
for op_id in 0..ops_per_thread {
let object = if threads == 1 {
format!("obj_{}_{}", thread_id, op_id)
} else {
format!("obj_{}", op_id % 100) // Create contention
};
let owner = format!("owner_{}", thread_id);
let _guard = manager_clone
.acquire_write_lock("bucket", object, owner)
.await
.unwrap();
}
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
let duration = start.elapsed();
let total_ops = threads * ops_per_thread;
let ops_per_sec = total_ops as f64 / duration.as_secs_f64();
println!("{}: {:.2} ops/sec", test_name, ops_per_sec);
// Performance targets (adjust based on requirements)
match test_name {
"single_thread" => assert!(ops_per_sec > 50000.0, "Single thread should exceed 50k ops/sec"),
"low_contention" => assert!(ops_per_sec > 20000.0, "Low contention should exceed 20k ops/sec"),
"high_contention" => assert!(ops_per_sec > 5000.0, "High contention should exceed 5k ops/sec"),
_ => {}
}
}
}
}