ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rust 编译器并行化深度解析:从 `-C codegen-units` 到 `-Z threads` 的并行前端

Rust 编译器并行化深度解析:从 `-C codegen-units` 到 `-Z threads` 的并行前端 Rust 编译器并行化深度解析从-C codegen-units到-Z threads的并行前端【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rustrustc 作为 Rust 生态的基石编译器其编译速度一直是开发者关注的核心痛点。本文基于 rustc 编译器开发指南rustc-dev-guide中 parallel-rustc 章节系统梳理 rustc 的并行编译现状、线程安全数据结构、并行迭代器与并行查询系统的设计与实现并结合当前仓库源码给出可验证的底层细节。读完本文你将掌握 rustc 中哪些阶段已经并行、如何通过-C codegen-units与-Z threads控制并行度、并行编译依赖的核心同步原语以及并行查询系统的工作原理与已知局限。注意按开发指南的说明截至 2024 年 11 月rustc 的并行前端正处于重大重构阶段跟踪 issue 见仓库内 rustc 上游 #113349本文部分描述可能滞后于最新代码指南亦标注其后若干小节内容已相当过时暂时保留。阅读时请以本仓库当前源码为准。rustc 并行化的总体格局截至 2024 年 11 月rustc 的大部分编译流程已经实现了并行化但三个大阶段的并行程度并不相同编译阶段并行情况控制方式代码生成codegen默认并发执行-C codegen-unitsn控制并发任务数HIR 之后的阶段类型检查、借用检查、MIR 优化等仅 nightly 版本支持默认串行需手动开启-Z threadsn词法解析、HIR lowering、宏展开等仍为串行执行无也就是说rustc 的并行化是后端默认并行、前端实验性并行、早期前端阶段仍串行的渐进式格局。-C codegen-units属于稳定的代码生成选项而-Z threads是需要 nightly 工具链的不稳定选项。在 rustc_session/src/options.rs 中可以看到-Z threads的官方描述为use --jobs-frontend instead说明该选项正处于被新选项取代的过渡期而codegen_units的说明则是divide crate into N units to optimize in parallel将 crate 划分为 N 个单元以并行优化见 options.rs。代码生成阶段codegen units 与并行 LLVM 实例在单态化monomorphization期间编译器把所有待生成的代码拆分成较小的块称为codegen unitsCGU。这些 CGU 随后由多个独立的 LLVM 实例并行生成最后再由链接器把所有 CGU 合并进同一个二进制文件。这一流程发生在rustc_codegen_ssa::base模块中。从源码可以印证该流程base.rs 中通过tcx.collect_and_partition_mono_items(())收集并划分单态化条目得到MonoItemPartitions { codegen_units, .. }——这正是把待生成代码拆分成 CGU的实现点随后 base.rs 对 CGU 列表进行排序、判断复用determine_cgu_reuse并逐个调用backend.compile_codegen_unit(...)交给后端LLVM编译。值得注意的细节是CGU 的数量直接影响并行度和优化效果CGU 越多并行度越高、编译期内存占用越低但跨单元内联cross-CGU inlining等优化机会变少可能牺牲运行期性能CGU 越少则反之。-C codegen-unitsn正是调节这一平衡的关键旋钮。线程安全数据结构同一类型两种实现并行编译器底层依赖的线程安全数据结构位于rustc_data_structures::sync模块。这些数据结构根据是否启用parallel-compiler特性编译 rustc 本身时是否开启并行而有完全不同的内部实现数据结构parallel并行non-parallel串行LockTparking_lot::MutexTstd::cell::RefCellRwLockTparking_lot::RwLockTstd::cell::RefCellReadGuardparking_lot::RwLockReadGuardstd::cell::RefMappedReadGuardparking_lot::MappedRwLockReadGuardstd::cell::RefWriteGuardparking_lot::RwLockWriteGuardstd::cell::RefMutMappedWriteGuardparking_lot::MappedRwLockWriteGuardstd::cell::RefMutLockGuardparking_lot::MutexGuardstd::cell::RefMut在串行编译器下这些类型退化为RefCell/Ref/RefMut零同步开销并行编译器下则切换为 parking_lot 提供的真实锁。源码中 sync.rs 直接pub use parking_lot::{MappedRwLockReadGuard as MappedReadGuard, ...}导出了 guard 类型而 lock.rs 实现的LockT更进一步它仅在可能处于多线程模式might_be_dyn_thread_safe时才真正启用RawMutex否则退化为一个Cellbool标记见 lock.rs 中的ModeUnion并把Send/Sync替换为自定义的DynSend/DynSynctrait。开发指南同时指出两个已知问题锁竞争导致性能回退这些线程安全数据结构散布于编译全程当线程数超过 4 时锁竞争可能引发性能下降。为此 rustc 团队会对这些数据结构的使用进行审计结果要么是重构以减少共享状态要么是编写持久化文档明确记录相关的不变量invariants、原子性约束与加锁顺序。不变量存疑编译过程中还有哪些在单线程下成立、并行下可能被破坏的不变量仍有待查明。此外sync.rs 还提供了一个#[repr(align(64))]的CacheAlignedT包装类型将每个 worker local 值按 64 字节对齐避免多线程下的伪共享false sharing问题——这是并行数据结构实现中的常见性能考量。WorkerLocal为并行查询服务的线程本地存储WorkerLocal是专为并行编译器实现的一种特殊数据结构它为线程池中的每个线程持有独立的 worker-local 值并且只能通过构造它的那个线程池上的Deref实现来访问否则会 panic。其实现要点见 worker_local.rs内部由一个Registry线程注册表和一段Box[CacheAlignedT]按线程索引排列的值数组组成Registry维护线程上限thread_limit与当前注册线程数线程通过register()注册并获得唯一索引Deref::deref通过RegistryId::verify()校验当前线程确属该注册表再以get_unchecked索引到对应值worker_local.rs由于每个线程只能访问自己的槽位WorkerLocalT被unsafe implT: Send Sync标注为Sync无需T自带同步worker_local.rs。WorkerLocal在并行环境中用于实现Arena分配器——这是并行查询系统中至关重要的一环每个线程在自己的 arena 中分配对象互不干扰。在非并行编译器里它退化为OneThreadTT可以直接通过Deref::deref访问。并行迭代器基于 rustc-rayon 的抽象并行编译器通过rayoncrate 提供的并行迭代器来实现并行化具体使用的是 rustc 团队维护的自定义 fork 版本 rustc-rayon。当parallel-compiler特性开启时下列迭代函数会被实现为并行执行函数省略Send/Sync约束作用所属模块par_iterT: IntoParallelIterator(t: T) - T::Iter生成并行迭代器rustc_data_structures::syncpar_for_each_inT: IntoParallelIterator(t: T, for_each: impl Fn(T::Item))生成并行迭代器并对每个元素执行for_eachrustc_data_structures::syncMap::par_body_owners(self, f: impl Fn(LocalDefId))对 crate 中所有 HIR owner 执行frustc_middle::hir::mapMap::par_for_each_module(self, f: impl Fn(LocalDefId))对 crate 中所有模块与子模块执行frustc_middle::hir::mapModuleItems::par_items(self, f: impl Fn(ItemId))对模块内所有 item 执行frustc_middle::hirModuleItems::par_trait_items(self, f: impl Fn(TraitItemId))对模块内所有 trait item 执行frustc_middle::hirModuleItems::par_impl_items(self, f: impl Fn(ImplItemId))对模块内所有 impl item 执行frustc_middle::hirModuleItems::par_foreign_items(self, f: impl Fn(ForeignItemId))对模块内所有 foreign item 执行frustc_middle::hir在 sync.rs 中可以看到这些并行原语的导出broadcast、par_fns、par_for_each_in、par_for_each_slice、par_join、par_map、parallel_guard、spawn、try_par_for_each_in。它们在底层通过rustc_thread_poolrustc-rayon 的线程池封装调度任务见 parallel.rs例如par_join在启用线程安全模式时调用rustc_thread_pool::join否则退化为串行serial_join。并行迭代器的实际应用场景编译器中存在大量可以用这些函数并行化的循环。截至 2022 年 8 月并行迭代器函数已被用于以下场景调用方场景被调用函数rustc_metadata::rmeta::encoder::prefetch_mir预取元数据编码后续需要的查询par_iterrustc_monomorphize::collector::collect_crate_mono_items收集从非泛型 item 可达的单态化条目par_for_each_inrustc_interface::passes::analysis校验 match 语句的有效性Map::par_body_ownersrustc_interface::passes::analysisMIR 借用检查Map::par_body_ownersrustc_typeck::check::typeck_item_bodies类型检查Map::par_body_ownersrustc_interface::passes::hir_id_validator::check_crate校验 HIR 有效性Map::par_for_each_modulerustc_interface::passes::analysis校验循环体、属性、naked 函数、不稳定 ABI、const 体Map::par_for_each_modulerustc_interface::passes::analysisMIR 的活跃性liveness与内建函数检查Map::par_for_each_modulerustc_interface::passes::analysisdeathness 检查Map::par_for_each_modulerustc_interface::passes::analysis隐私privacy检查Map::par_for_each_modulerustc_lint::late::check_crate执行按模块划分的 lintMap::par_for_each_modulerustc_typeck::check_cratewell-formedness 检查Map::par_for_each_module可以观察到类型检查typeck_item_bodies、MIR 借用检查、活跃性检查、隐私检查等按 body 或按模块可独立计算的任务是并行化的主要受益者而仍然存在大量有潜力使用并行迭代器但尚未改造的循环。并行查询系统不可变结果 按查询粒度加锁rustc 的查询query模型拥有两个关键性质使得并行求值多个查询成为可能查询 provider 只能通过查询上下文query context访问数据因此上下文可以统一负责同步访问查询结果要求不可变从而可以被不同线程安全地并发使用。当求值查询foo时foo的缓存表会被加锁并按下述规则处理已有结果克隆结果、释放锁、完成无缓存且无其他线程正在计算同一结果将该 key 标记为 in progress进行中释放锁开始求值有其他线程正在计算同一 key释放锁本线程阻塞直到对方计算完毕并取得结果。一个值得强调的复杂点是循环错误检测cycle error detection并行编译器下的循环检测比单线程模式复杂得多。当并行查询中的多个 worker 线程因相互依赖而停止推进死锁时编译器会动用一条**额外的线程称为 deadlock handler死锁处理器**来检测、移除并报告循环错误。需要澄清的是指南中描述的这套基于锁缓存表 in-progress 标记的查询调度模型属于该文档写作时期的实现思路而指南同时说明并行查询特性仍有大量实现工作待完成且大多与前述数据结构和并行迭代器部分相关并指向其跟踪 issue仓库内 rustc 上游 #48685。从仓库源码还可以看到线程模式是如何被开启的interface.rs 中通过rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.jobs.frontend.is_some())依据前端任务数决定是否进入动态线程安全模式util.rs 则用jobs_frontend配置线程池的线程数并在创建失败时提示try lowering-Z threadsor checking the operating systems resource limits尝试降低-Z threads或检查操作系统资源限制。这印证了-Z threads当前在源码中对应的实际开关逻辑。Rustdoc 的并行化状态截至 2022 年 11 月rustdoc渲染在真正并行化之前仍有许多步骤需要完成见 rustc 上游关于并行 rustdoc 的公开讨论 issue #82741。也就是说虽然 rustc 编译本身已大面积并行但文档生成工具 rustdoc 的并行化仍在规划之中属于尚未落地的方向。深入学习的资源开发指南最后列出了一些学习并行 rustc 的参考资源此处归纳其主题原文均为外部链接仓库内不包含其内容alexchricton 在 IRLOinternals.rust-lang.org上关于并行 rustc 性能的讨论帖Zoxc该并行化工作的先驱之一在 IRLO 上关于使用 Rayon 并行化 rustc的讨论帖nikomatsakis 整理的编译器内部可变性interior mutability清单。总结rustc 的并行化是一条自底向上、逐步推进的路线后端codegen早已默认并行由稳定的-C codegen-unitsn控制拆分粒度与并发度实现在 rustc_codegen_ssa::base**前端中后端类型检查、借用检查、MIR 优化等**在 nightly 上通过-Z threadsn手动开启其线程开关逻辑见 interface.rs并行基础设施Lock/RwLock/guard、WorkerLocal、并行迭代器、并行查询调度集中在 rustc_data_structures::sync随parallel-compiler特性在并行/串行两种实现间切换词法解析、HIR lowering、宏展开以及 rustdoc 渲染仍处于串行状态是未来并行化的方向。对于普通用户最直接的收益来自稳定的-C codegen-units而想要体验前端并行、进一步压榨编译吞吐的开发者可以在 nightly 工具链上尝试-Z threads并留意该选项正在向--jobs-frontend迁移的过渡现状。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表