docs: bilingual comments for the benchmark examples
This commit is contained in:
parent
2190e1f81f
commit
a4f5a30aa9
|
|
@ -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<F, T>(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)])
|
||||
|
|
|
|||
|
|
@ -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<F, T>(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<F, T>(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<OrbitSample> {
|
||||
(0..samples)
|
||||
.map(|index| {
|
||||
|
|
@ -137,6 +182,10 @@ fn track(satellite: &Satellite, samples: usize, epoch_mjd: f64) -> Vec<OrbitSamp
|
|||
/// Propagates a whole constellation over a day, serially.
|
||||
///
|
||||
/// The serial time is the reference every parallel number is divided by.
|
||||
///
|
||||
/// 串行地把整个星座在一天上传播。
|
||||
///
|
||||
/// 该串行时间是每个并行数字所除以的基准。
|
||||
fn propagate_serial(satellite: &Satellite, satellites: usize, samples: usize) -> 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<OrbitSample>], finder: &AccessFinder) -> usize {
|
||||
let mut passes = 0_usize;
|
||||
for track in tracks {
|
||||
|
|
@ -206,6 +263,10 @@ fn access_serial(tracks: &[Vec<OrbitSample>], 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<OrbitSample>], 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<OrbitSample>], 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<Vec<(usize, usize)>> = 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<Vec<OrbitSample>> = (0..satellites)
|
||||
|
|
@ -383,6 +462,7 @@ fn main() {
|
|||
}
|
||||
|
||||
// ---- Three: topology construction. ------------------------------------
|
||||
// ---- 三:拓扑构建。--------------------------------------------
|
||||
{
|
||||
for &satellites in &[120_usize, 300] {
|
||||
let positions: Vec<Vec3> = (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)"
|
||||
|
|
|
|||
|
|
@ -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<F, T>(iterations: u32, mut path: F) -> Vec<Duration>
|
||||
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<OrbitSample> {
|
||||
(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<Vec3> = (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] {
|
||||
|
|
|
|||
Loading…
Reference in New Issue