From 956403cc88d005afd960549ed6cab798b3a28044 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 01:54:29 +0800 Subject: [PATCH] docs: bilingual comments for the benchmark examples --- crates/apsis-rs/examples/bench.rs | 43 +++++++++++ crates/apsis-rs/examples/bench_parallel.rs | 83 ++++++++++++++++++++++ crates/apsis-rs/examples/bench_scale.rs | 55 ++++++++++++++ 3 files changed, 181 insertions(+) diff --git a/crates/apsis-rs/examples/bench.rs b/crates/apsis-rs/examples/bench.rs index 7a0b406..9a50b50 100644 --- a/crates/apsis-rs/examples/bench.rs +++ b/crates/apsis-rs/examples/bench.rs @@ -18,6 +18,22 @@ //! Only paths that a caller is likely to run in a loop are included: //! propagation, ephemeris interpolation, access search, link budget, and //! task planning. A path that runs once per program is not worth timing. +//! +//! 热点路径的微基准,无外部依赖。 +//! +//! Rust 内建的基准测试框架仅在 nightly 可用,而通常的替代是第三方 crate,本 +//! 工作区禁止它。本示例就是替代品:它用标准库的单调时钟为每条路径计时并打印一张 +//! 表。它意在 release 模式下运行,那里的计时才有意义: +//! +//! ```text +//! cargo run --release -p apsis-rs --features full --example bench +//! ``` +//! +//! 每条路径运行的次数足够多,使所测时间远高于时钟分辨率,而所报告的数字是总时间 +//! 除以迭代次数。这些数字是相对代价的指引,不是用绝对值跨机器比较的基准。 +//! +//! 只纳入调用方可能在循环中运行的路径:传播、星历插值、可见性搜索、链路预算,以及 +//! 任务规划。每个程序只运行一次的路径不值得计时。 use apsis_access::{AccessFinder, OrbitSample}; use apsis_coord::{Ecef, Eci}; @@ -42,15 +58,24 @@ use std::time::Instant; /// The value is chosen so that every measurement is at least a few /// milliseconds, which puts it far above the clock resolution while keeping /// the whole run under a second per path. +/// +/// 每条路径运行的次数。 +/// +/// 该值的选取使每次测量至少有几毫秒,这使它远高于时钟分辨率,同时使每条路径的 +/// 整次运行不到一秒。 const ITERATIONS: u32 = 20_000; /// The element set used for the propagation and access paths. +/// +/// 用于传播与可见性路径的根数集。 const ISS: &str = concat!( "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n", "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n", ); /// The number of seconds from the epoch the sample orbit starts at. +/// +/// 样本轨道所起始的距历元秒数。 const EPOCH_MJD: f64 = 54_723.517_825_28; /// Runs a path a number of times and returns the mean duration in @@ -58,11 +83,16 @@ const EPOCH_MJD: f64 = 54_723.517_825_28; /// /// The closure runs once before the clock starts, so that any lazy /// initialisation it does is not charged to the first iteration. +/// +/// 把一条路径运行若干次并返回以纳秒计的平均时长。 +/// +/// 该闭包在时钟启动前运行一次,使它所做的任何惰性初始化不会计入第一次迭代。 fn measure(iterations: u32, mut path: F) -> f64 where F: FnMut() -> T, { // Warm up. + // 预热。 black_box(path()); let start = Instant::now(); for _ in 0..iterations { @@ -73,6 +103,8 @@ where } /// Prints one row of the table. +/// +/// 打印表中的一行。 fn report(name: &str, nanos: f64, iterations: u32) { println!("{name:<44} {nanos:>12.1} ns ({iterations} iterations)"); } @@ -82,6 +114,7 @@ fn main() { println!("{:<44} {:>12} (iterations)", "path", "mean"); // ---- SGP4 propagation ------------------------------------------------- + // ---- SGP4 传播 ------------------------------------------------- let element = TwoLineElement::parse(ISS).expect("the sample parses"); let satellite = Satellite::from_element(&element, GravityModel::WGS72).expect("the orbit propagates"); @@ -93,6 +126,7 @@ fn main() { report("sgp4 propagate (30 min from epoch)", nanos, ITERATIONS); // ---- Two-body Keplerian conversion ------------------------------------ + // ---- 二体开普勒换算 ------------------------------------------ let elements = KeplerianElements { semi_major_axis: 7_000.0, eccentricity: 0.01, @@ -109,6 +143,7 @@ fn main() { report("kepler elements to state", nanos, ITERATIONS); // ---- Hermite interpolation -------------------------------------------- + // ---- Hermite 插值 --------------------------------------------- let sampler = Sampler::new(0.0, 600.0, 60.0); let ephemeris = sampler .sample(|seconds| { @@ -131,12 +166,14 @@ fn main() { report("sgp4 propagate (comparison query)", nanos, ITERATIONS); // ---- Earth-fixed rotation -------------------------------------------- + // ---- 地固转动 ------------------------------------------------- let angle = EarthRotationAngle::from_ut1_days(EPOCH_MJD - 51_544.5); let eci = Eci::new(apsis_math::Vec3::new(7_000.0, 0.0, 0.0)); let nanos = measure(ITERATIONS, || eci.to_ecef(black_box(angle)).vector()); report("TEME to Earth-fixed rotation", nanos, ITERATIONS); // ---- Geodetic conversion --------------------------------------------- + // ---- 大地换算 ------------------------------------------------- let ecef = Ecef::new(apsis_math::Vec3::new(6_878_137.0, 0.0, 0.0)); let nanos = measure(ITERATIONS, || { ecef.to_geodetic(black_box(apsis_coord::WGS84)) @@ -147,6 +184,8 @@ fn main() { // ---- Access search ---------------------------------------------------- // A minute of samples of a low Earth orbit, which is the input an access // search consumes. + // ---- 可见性搜索 ---------------------------------------------- + // 低轨一分钟的样本,即可见性搜索所消费的输入。 let mut samples = Vec::with_capacity(120); for index in 0..120 { let seconds = f64::from(index) * 30.0; @@ -166,6 +205,7 @@ fn main() { report("access window search (120 samples)", nanos, 2_000); // ---- Link budget ------------------------------------------------------ + // ---- 链路预算 ------------------------------------------------- let budget = LinkBudget { transmit_power: Power::from_dbw(10.0).expect("finite"), transmit_gain: Gain::from_dbi(30.0).expect("finite"), @@ -188,6 +228,9 @@ fn main() { // One station and sixty tasks, which is a small but realistic planning // cycle. The whole plan is timed rather than one placement, because a // caller runs the planner once per cycle. + // ---- 任务规划 ------------------------------------------------- + // 一座站与六十个任务,这是一个小但真实的规划周期。计时的是整个计划而非单次 + // 放置,因为调用方每个周期运行规划器一次。 let mut station = Resource::new(ResourceId::new(0), 1.0); station .set_windows(&[Window::new(0.0, 6_000.0)]) diff --git a/crates/apsis-rs/examples/bench_parallel.rs b/crates/apsis-rs/examples/bench_parallel.rs index bb790b5..d6f0d07 100644 --- a/crates/apsis-rs/examples/bench_parallel.rs +++ b/crates/apsis-rs/examples/bench_parallel.rs @@ -26,6 +26,26 @@ //! measured cold, so the comparison is between two numbers the caller could //! actually observe. //! +//! 并行规模基准:热点路径对多核的利用程度。 +//! +//! 串行的 `bench_scale` 示例测量一条路径的代价。本示例问下一个问题:当工作铺到 +//! 多个核上时,那节省了多少墙钟时间,以及并行版本从何处起不再值得。 +//! +//! 所选的路径是真实运行会并行的那些,因为每一条或是可平凡并行,或有明确的串行 +//! 尾部: +//! +//! - **星座传播**独立地传播每颗卫星,故它是可并行化的,应当近似线性扩展。 +//! - **可见性搜索**独立地搜索每条轨迹,同样如此。 +//! - **拓扑构建**独立地判定每对卫星,但把结果折进一张图是串行的,故它是展示 +//! 串行尾部代价的用例。 +//! +//! 计时在串行与一系列线程数上比较相同的总工作,并报告相对串行时间与理想线性时间 +//! 的加速比。二者不同,其差就是并行开销:线程创建、工作不均衡与串行尾部。 +//! +//! 不预热,理由见 `bench_scale`:第一次调用支付该路径在首次使用时分配的任何东西, +//! 而调用方也要为它等待。串行时间与并行时间都冷测,故比较的是调用方实际所能观察到 +//! 的两个数字。 +//! //! Usage: //! //! ```text @@ -46,18 +66,26 @@ use std::hint::black_box; use std::time::{Duration, Instant}; /// The element set used as the source of every propagated track. +/// +/// 用作每条所传播轨迹来源的根数集。 const ISS: &str = concat!( "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n", "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n", ); /// The body radius, in kilometers, taken from the shared constants. +/// +/// 天体半径,单位为千米,取自共享常量。 const BODY_RADIUS_KM: f64 = apsis_core::constant::EARTH_EQUATORIAL_RADIUS; /// The sample step, in seconds. +/// +/// 采样步长,单位为秒。 const TRACK_STEP_SECONDS: f64 = 60.0; /// Runs a path once and returns how long it took. +/// +/// 把一条路径运行一次并返回其耗时。 fn measure(path: F) -> Duration where F: FnOnce() -> T, @@ -74,6 +102,12 @@ where /// scheduling hiccup on one run cannot inflate the reference every later /// number is divided by. The cold cost of the very first call is still /// reported on its own, so nothing is hidden. +/// +/// 把一条路径运行 `repeats` 次并返回最快的时长。 +/// +/// 若干次运行中最快的一次受机器上其他活动污染最少,且它是干净扩展的数字,因为某 +/// 一次运行上的调度打嗝无法抬高其后每个数字所除以的基准。第一次调用的冷代价仍单独 +/// 报告,故没有任何隐藏。 fn measure_best(repeats: u32, path: F) -> Duration where F: Fn() -> T, @@ -93,6 +127,11 @@ where /// The efficiency is the speedup divided by the thread count, so one means /// the path used every core fully. A value well below one means the threads /// were idle, which is what a serial tail looks like. +/// +/// 打印一行。 +/// +/// 效率是加速比除以线程数,故一意味着该路径充分利用了每个核。远低于一的值意味着 +/// 各线程有空闲,这正是串行尾部的样子。 fn row(threads: usize, wall: Duration, work: Duration, cores: usize) { let wall_ms = wall.as_secs_f64() * 1e3; let work_ms = work.as_secs_f64() * 1e3; @@ -104,12 +143,16 @@ fn row(threads: usize, wall: Duration, work: Duration, cores: usize) { } /// Returns a prepared satellite. +/// +/// 返回一颗准备好的卫星。 fn satellite() -> Satellite { let element = TwoLineElement::parse(ISS).expect("the sample parses"); Satellite::from_element(&element, GravityModel::WGS72).expect("the orbit propagates") } /// The epoch of the sample element set, as a Modified Julian Date. +/// +/// 样本根数集的历元,以约化儒略日表示。 fn epoch_mjd() -> f64 { let element = TwoLineElement::parse(ISS).expect("the sample parses"); element @@ -119,6 +162,8 @@ fn epoch_mjd() -> f64 { } /// Returns a track of Earth-fixed samples for one satellite. +/// +/// 返回一颗卫星的地固样本轨迹。 fn track(satellite: &Satellite, samples: usize, epoch_mjd: f64) -> Vec { (0..samples) .map(|index| { @@ -137,6 +182,10 @@ fn track(satellite: &Satellite, samples: usize, epoch_mjd: f64) -> Vec usize { let mut count = 0_usize; for _ in 0..satellites { @@ -158,6 +207,12 @@ fn propagate_serial(satellite: &Satellite, satellites: usize, samples: usize) -> /// evenly and no two threads write the same state. The `scope` form of /// spawning borrows the satellite rather than requiring it to outlive the /// threads, which is what lets the reference be shared without cloning. +/// +/// 跨 `threads` 个线程把整个星座在一天上传播。 +/// +/// 每个线程取卫星的一个互不相交切片,故工作均匀分开,且没有两个线程写相同的状态。 +/// `scope` 形式的创建借用该卫星,而不要求它比各线程活得久,这正是使参考无需克隆 +/// 即可共享的原因。 fn propagate_parallel( satellite: &Satellite, satellites: usize, @@ -194,6 +249,8 @@ fn propagate_parallel( } /// Searches access for every track, serially. +/// +/// 串行地为每条轨迹搜索可见性。 fn access_serial(tracks: &[Vec], finder: &AccessFinder) -> usize { let mut passes = 0_usize; for track in tracks { @@ -206,6 +263,10 @@ fn access_serial(tracks: &[Vec], finder: &AccessFinder) -> usize { /// /// The tracks are read only and the finder is a copy, so each thread can /// take a disjoint slice without synchronisation. +/// +/// 跨 `threads` 个线程为每条轨迹搜索可见性。 +/// +/// 各轨迹是只读的,且查找器是一份副本,故每个线程可取一个互不相交切片而无需同步。 fn access_parallel(tracks: &[Vec], finder: &AccessFinder, threads: usize) -> usize { let chunk = tracks.len().div_ceil(threads); let total = std::sync::atomic::AtomicUsize::new(0); @@ -231,6 +292,8 @@ fn access_parallel(tracks: &[Vec], finder: &AccessFinder, threads: } /// Builds a topology serially, deciding each pair and folding it in. +/// +/// 串行地构建拓扑,判定每一对并将其折入。 fn topology_serial(positions: &[Vec3], satellites: usize, range: f64) -> usize { let mut topology = Topology::new(satellites); for first in 0..satellites { @@ -252,6 +315,11 @@ fn topology_serial(positions: &[Vec3], satellites: usize, range: f64) -> usize { /// visible pairs in its own slice and returns them. Folding them into the /// single graph is serial, which is the point of measuring this path: it /// shows how much of the time the parallel part actually saves. +/// +/// 跨 `threads` 个线程构建拓扑。 +/// +/// 每对的几何是独立的,故每个线程计算其自身切片中的可见对并返回它们。把它们折进 +/// 单一图是串行的,这正是测量本路径的要点:它显示并行部分实际节省了多少时间。 fn topology_parallel(positions: &[Vec3], satellites: usize, range: f64, threads: usize) -> usize { let chunk = satellites.div_ceil(threads); let results: Vec> = std::thread::scope(|scope| { @@ -282,6 +350,7 @@ fn topology_parallel(positions: &[Vec3], satellites: usize, range: f64, threads: }); // The serial tail: fold every visible pair into one graph. + // 串行尾部:把每个可见对折进一张图。 let mut topology = Topology::new(satellites); for pairs in &results { for &(first, second) in pairs { @@ -298,6 +367,12 @@ fn topology_parallel(positions: &[Vec3], satellites: usize, range: f64, threads: /// embarrassingly parallel, the second is a serial pass whose cost grows /// with the square of the edge count because each insertion checks for a /// duplicate by scanning the edges already stored. +/// +/// 把拓扑构建拆成其几何阶段与折入阶段。 +/// +/// 返回可见性判定的时间与把可见对折进图的时间,均以毫秒计。第一个是可并行化的, +/// 第二个是串行的一遍,其代价随边数的平方增长,因为每次插入都通过扫描已存的边来 +/// 检查重复。 fn topology_phase_split(positions: &[Vec3], satellites: usize, range: f64) -> (f64, f64) { let start = Instant::now(); let mut pairs = Vec::new(); @@ -336,6 +411,8 @@ fn main() { // The thread counts swept: one is the serial reference, then the powers // of two up to the machine, then the machine's own count. + // 所扫过的线程数:一是串行基准,然后是到该机器为止的二的幂,再是该机器自身的 + // 数量。 let mut thread_counts = vec![1_usize, 2, 4, 8]; if cores > 8 { thread_counts.push(cores); @@ -344,6 +421,7 @@ fn main() { thread_counts.dedup(); // ---- One: constellation propagation over a day. ---------------------- + // ---- 一:在一天上传播星座。------------------------------------ { let samples = 1_441_usize; // one day at a one minute step for &satellites in &[550_usize, 1_000, 2_000] { @@ -362,6 +440,7 @@ fn main() { } // ---- Two: access search. --------------------------------------------- + // ---- 二:可见性搜索。------------------------------------------ { for &satellites in &[64_usize, 256] { let tracks: Vec> = (0..satellites) @@ -383,6 +462,7 @@ fn main() { } // ---- Three: topology construction. ------------------------------------ + // ---- 三:拓扑构建。-------------------------------------------- { for &satellites in &[120_usize, 300] { let positions: Vec = (0..satellites) @@ -402,6 +482,9 @@ fn main() { // the graph is a serial pass whose cost is superlinear in the // edge count. Reporting the two separately is the difference // between "parallelism did not help" and knowing why. + // 该拆分显示时间实际去了哪里:几何可见性判定是并行的,但把可见对折进图是串行的 + // 一遍,其代价在边数上超线性。把二者分开报告,正是「并行没帮上忙」与知其所以然 + // 之间的区别。 let (vis_ms, fold_ms) = topology_phase_split(&positions, satellites, 4_500.0); println!( " split: visibility {vis_ms:.3} ms (parallel), edge fold {fold_ms:.3} ms (serial tail)" diff --git a/crates/apsis-rs/examples/bench_scale.rs b/crates/apsis-rs/examples/bench_scale.rs index d07543b..be44125 100644 --- a/crates/apsis-rs/examples/bench_scale.rs +++ b/crates/apsis-rs/examples/bench_scale.rs @@ -19,6 +19,19 @@ //! own column beside the steady state rather than being hidden behind a //! discarded run. //! +//! 针对真实工作负载所重压路径的规模基准。 +//! +//! 配套的 `bench` 示例测量单次调用的代价,回答某操作是否快。本示例测量该代价如何 +//! 随问题规模增长,这是决定一次星座规模的运行是否可行的问题。它在二的幂上扫过输入 +//! 规模并报告每单位工作的用时,故即便绝对数字随机器变化,增长率的变化也会显现。 +//! +//! 各路径正是规划周期实际运行的:传播一个 Walker 星座、为一座站在每颗卫星上搜索 +//! 可见性窗口、构建星间拓扑并在其中路由,以及规划一个带先后约束的任务集。 +//! +//! 一切都在 release 模式下测量。有意不预热:第一次调用支付该路径在首次使用时分配 +//! 或缓存的任何东西,而调用方也要为它等待,故冷代价与稳态并排报告在自己的列中, +//! 而非隐藏在某个被丢弃的运行之后。 +//! //! Usage: //! //! ```text @@ -43,15 +56,21 @@ use std::hint::black_box; use std::time::{Duration, Instant}; /// The element set used as the source of every propagated track. +/// +/// 用作每条所传播轨迹来源的根数集。 const ISS: &str = concat!( "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n", "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n", ); /// The body radius, in kilometers, taken from the shared constants. +/// +/// 天体半径,单位为千米,取自共享常量。 const BODY_RADIUS_KM: f64 = apsis_core::constant::EARTH_EQUATORIAL_RADIUS; /// The sample step of a propagated track, in seconds. +/// +/// 所传播轨迹的采样步长,单位为秒。 const TRACK_STEP_SECONDS: f64 = 60.0; /// Runs a path a number of times and returns the duration of each call. @@ -61,6 +80,12 @@ const TRACK_STEP_SECONDS: f64 = 60.0; /// run would report a number the caller never actually waits for. The first /// duration is therefore kept and reported separately as the cold cost, and /// the rest are the steady state. +/// +/// 把一条路径运行若干次并返回每次调用的时长。 +/// +/// 有意不预热。第一次调用支付该路径在首次使用时分配或缓存的任何东西,而把它隐藏 +/// 在一个被丢弃的运行之后会报告一个调用方从未实际等待过的数字。故第一个时长被保留 +/// 并作为冷代价单独报告,其余的是稳态。 fn measure(iterations: u32, mut path: F) -> Vec where F: FnMut() -> T, @@ -78,6 +103,10 @@ where /// /// The first call is excluded because it carries the cold cost, which is /// reported on its own column. +/// +/// 一组时长的均值,忽略第一个。 +/// +/// 第一次调用被排除,因为它携带冷代价,后者在自己的列中报告。 fn steady_mean(durations: &[Duration]) -> Duration { if durations.len() <= 1 { return durations.first().copied().unwrap_or_default(); @@ -91,6 +120,11 @@ fn steady_mean(durations: &[Duration]) -> Duration { /// The columns are the cold cost of the first call, the mean of the rest, and /// the steady cost per unit of work. A cold cost far above the steady mean /// means the path allocates or caches something on first use. +/// +/// 打印扫描表中的一行。 +/// +/// 各列是第一次调用的冷代价、其余者的均值,以及每单位工作的稳态代价。冷代价远高于 +/// 稳态均值意味着该路径在首次使用时分配或缓存了某些东西。 fn row(label: &str, size: usize, per_unit_work: f64, durations: &[Duration]) { let cold = durations.first().copied().unwrap_or_default(); let steady = steady_mean(durations); @@ -107,6 +141,8 @@ fn row(label: &str, size: usize, per_unit_work: f64, durations: &[Duration]) { } /// The header of the sweep table. +/// +/// 扫描表的表头。 fn header(title: &str) { println!("\n{title}"); println!( @@ -116,12 +152,16 @@ fn header(title: &str) { } /// Returns a prepared satellite. +/// +/// 返回一颗准备好的卫星。 fn satellite() -> Satellite { let element = TwoLineElement::parse(ISS).expect("the sample parses"); Satellite::from_element(&element, GravityModel::WGS72).expect("the orbit propagates") } /// The epoch of the sample element set, as a Modified Julian Date. +/// +/// 样本根数集的历元,以约化儒略日表示。 fn epoch_mjd() -> f64 { let element = TwoLineElement::parse(ISS).expect("the sample parses"); element @@ -131,6 +171,8 @@ fn epoch_mjd() -> f64 { } /// Returns a track of Earth-fixed samples for one satellite. +/// +/// 返回一颗卫星的地固样本轨迹。 fn track(satellite: &Satellite, samples: usize, epoch_mjd: f64) -> Vec { (0..samples) .map(|index| { @@ -157,10 +199,14 @@ fn main() { // The path a coverage run actually performs: build the constellation, // then propagate every satellite in it once per sample across a day. // The sample step is sixty seconds, so a day is 1441 samples. + // ---- 一:在一天上采样一个星座。-------------------------------- + // 覆盖运行实际执行的路径:构建星座,然后在一整天上对其中每颗卫星每个样本传播 + // 一次。采样步长为六十秒,故一天是 1441 个样本。 { header("one: build a constellation and sample every satellite over 1 day (60 s step)"); // Each entry is (planes, satellites per plane), chosen so the total // divides evenly, which the pattern requires. + // 每项是(轨道面数,每面卫星数),其选取使总数整除,这是该模式所要求的。 for &(planes, per_plane) in &[ (3_usize, 8_usize), (6, 11), @@ -178,6 +224,7 @@ fn main() { semi_major_axis: BODY_RADIUS_KM + 550.0, }; // A day at a one minute step is 1441 samples per satellite. + // 一分钟步长的一天是每颗卫星 1441 个样本。 let samples = 1_441_usize; let iterations = if satellites <= 240 { 3 } else { 1 }; let tracker = &satellite; @@ -187,6 +234,8 @@ fn main() { // Every satellite is propagated at its own orbital elements, // so the deep space branch is exercised where the period is // long, exactly as a real constellation would. + // 每颗卫星都按其自身轨道根数传播,故在周期长的地方深层空间分支被检验,正如真实 + // 星座那样。 let mut propagated = 0_usize; for orbit in constellation.satellites() { for index in 0..samples { @@ -197,6 +246,8 @@ fn main() { // same, and using one keeps the loop free of a second // initialisation that would be charged to the cold // column. + // 参考卫星代行星座各成员,它们的根数只在轨道面上不同;传播代价相同,而使用一个 + // 参考使该循环不含有会被计入冷列的第二次初始化。 let state = tracker .propagate_minutes(seconds / 60.0) .expect("the track propagates"); @@ -213,6 +264,7 @@ fn main() { } // ---- Two: access search over N tracks. ------------------------------ + // ---- 二:在 N 条轨迹上的可见性搜索。-------------------------- { header("two: access search for one station over N tracks (2 h each)"); for &satellites in &[8_usize, 32, 64, 128] { @@ -237,11 +289,13 @@ fn main() { } // ---- Three: topology and routing over N satellites. ------------------ + // ---- 三:N 颗卫星上的拓扑与路由。----------------------------- { header("three: build topology over N satellites and route once"); for &satellites in &[8_usize, 24, 64, 120, 300, 600] { // Positions spread on a ring so a fixed link range keeps a // bounded number of neighbours, as a real shell does. + // 位置分布在一个环上,使固定的链路距离保持邻居数有界,正如真实壳层那样。 let positions: Vec = (0..satellites) .map(|index| { let angle = index as f64 * 0.4; @@ -286,6 +340,7 @@ fn main() { } // ---- Four: plan N tasks on one resource. ----------------------------- + // ---- 四:在一个资源上规划 N 个任务。-------------------------- { header("four: plan N tasks on one resource, with a 50 long chain"); for &tasks in &[60_usize, 200, 500, 1_000, 2_000] {