Rust 凭借其速度和高效的线程处理能力而闻名。今天,我们将通过一个例子来测试 Rust 多线程的实际性能。
问题: 找到一个非常大的数字的所有质因数。
回顾: 因数是指能被另一个数整除且没有余数的数。例如,10 的因数是 1,2,5 和 10。90 的因数是 1,2,3,5,6,9,10,15,18,30,45 和 90。
我们的代码将先找到一个数字的所有因数,然后判断这些因数是否是质数。
第 1 部分:不使用多线程
步骤 1:编写寻找因数的函数
fn find_factors(n: u64) -> Vec<u64> {
let mut factors = Vec::new();
let square_root = (n as f64).sqrt() as u64;
for i in 2..=square_root {
if n % i == 0 {
factors.push(i);
}
}
factors
}
步骤 2:添加质数判断
fn find_factors(n: u64) -> Vec<u64> {
let mut factors = Vec::new();
let mut prime_factors = Vec::new();
let square_root = (n as f64).sqrt() as u64;
for i in 2..=square_root {
if n % i == 0 {
factors.push(i);
}
}
factors.iter().for_each(|&factor| {
if is_prime(factor) {
prime_factors.push(factor);
}
});
prime_factors
}
fn is_prime(n: u64) -> bool {
if n < 2 {
return false;
}
for i in 2..=(n as f64).sqrt() as u64 {
if n % i == 0 {
return false;
}
}
true
}
步骤 3:测试执行
use std::time::Instant;
fn main() {
let numbers = [100, 200, 8054223232324444426];
for i in numbers {
let start = Instant::now();
println!("{:?}", find_factors(i));
let duration = start.elapsed();
println!("Time elapsed: {:?}", duration);
}
}
输出:
[2, 5]
Time elapsed: 34.458µs
[2, 5]
Time elapsed: 4µs
[2, 14923]
Time elapsed: 16.687481416s
第 2 部分:使用多线程
现在,我们将把质数判断函数改造成多线程操作。我们将定义要使用的线程数,将因数集合平均分配到各个线程,并将它们发送到不同的线程进行处理。
步骤 1:编写新的多线程质数判断函数
use std::thread;
fn is_prime_mt(n: u64) -> bool {
if n < 2 {
return false;
}
let sqrt_n = (n as f64).sqrt() as u64;
let num_threads = 4;
let chunk_size = sqrt_n / num_threads;
let mut handles = vec![];
for i in 0..num_threads {
let start = i * chunk_size + 2;
let end = if i == num_threads - 1 { sqrt_n } else { (i + 1) * chunk_size + 1 };
let handle = thread::spawn(move || {
for divisor in start..=end {
if n % divisor == 0 {
return false;
}
}
true
});
handles.push(handle);
}
for handle in handles {
if let Ok(is_prime_in_chunk) = handle.join() {
if !is_prime_in_chunk {
return false;
}
}
}
true
}
步骤 2:更新 find_factors
函数以使用新的多线程质数判断
fn find_factors(n: u64) -> Vec<u64> {
let mut factors = Vec::new();
let mut prime_factors = Vec::new();
let square_root = (n as f64).sqrt() as u64;
for i in 2..=square_root {
if n % i == 0 {
factors.push(i);
}
}
factors.iter().for_each(|&factor| {
if is_prime_mt(factor) {
prime_factors.push(factor);
}
});
prime_factors
}
步骤 3:测试新的多线程解决方案
[2, 5]
Time elapsed: 471.166µs
[2, 5]
Time elapsed: 464.208µs
[2, 14923]
Time elapsed: 16.587179166s
咦? 这与预期结果不符,但可以理解。因为线程本身也需要开销,所以对于简单的任务来说,创建和管理线程的成本会超过并发带来的收益。
在这个例子中,由于计算量很轻,创建和管理线程的开销超过了并发的优势。相反,当问题涉及更复杂的计算或更大的数据集时,线程可以带来显著的性能提升。
什么时候应该考虑使用线程?
在以下情况下,你应该考虑使用线程:
任务计算量很大,或者可以分割成更小的独立块。 程序涉及大量等待(例如网络请求、文件 I/O),线程可以在等待时处理其他任务。 你正在处理大型数据集或进行繁重的计算,并行处理可以显著减少执行时间。
在像这个例子这样的简单情况下,单线程执行通常更有效。