# parallel_lab_p **Repository Path**: ykurin/parallel_lab_p ## Basic Information - **Project Name**: parallel_lab_p - **Description**: MFPIR 《并行编程综合实践》实验1 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-07-04 - **Last Updated**: 2026-07-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 《并行编程综合实践》实验:图书馆扫描件批量转码 --- > 当前文档:Lab_Instruction.html / README.md > 并行编程基础:basics/basics.html / basics/basics.md ## 实验目的 1. 理解 I/O 密集型与 CPU 密集型任务混合场景下的并行加速策略。 2. 线程池、信号量、锁、队列等同步原语的实际应用。 3. 实践**流水线并行**思想,使网络 I/O 与 CPU 计算在多任务间重叠执行。 4. 通过逐步迭代(串行 → 页级并行 → 流水线),直观理解 Amdahl 定律及资源竞争对加速比的影响。 ## 实验环境 - **语言**:Python 3.10+ - **依赖**:仅标准库(`threading`、`concurrent.futures`、`queue` 等),无需安装第三方包。 - **代码**:核心框架托管在`Gitee`上,已开源:`https://gitee.com/ykurin/parallel_lab_p.git` / `git@gitee.com:ykurin/parallel_lab_p.git` - **数据集**:`data/books.csv`(默认),可通过`generate_data.py`生成,并通过 `--dataset` 参数切换。每行格式为 `book_id,page_id,size_mb`。 - **模拟层**:`_common.py` 封装所有网络/CPU 模拟行为,通过 `time.sleep()` 模拟耗时,`SPEEDUP` 因子压缩 wall-clock 时间。 - **代码模板**:`main.py`(空实现) - **参考脚本**:`baseline.py`(串行基线)。 ## 实验要求 1. 仔细阅读本手册,理解数据模型与处理流程。(也可阅读 `_common.py` 源码及注释) 2. 从串行实现开始,依次实现**仅编码并行**与**流水线并行**,程序会自动记录每次的耗时与加速比。(编码模板参考`main.py`及`baseline.py`) 3. 正确处理资源保护——网络连接超限应可恢复重试,CPU 核心超限必须杜绝。 4. 可使用 Python 标准库中任意同步原语,**不得修改 `_common.py`**。 5. 撰写实验报告,包含但不限于方案设计、关键代码片段、实验结果对比及分析、实验中遇到的异常及解决方案等。 --- ## 背景描述 图书馆将馆藏旧书逐页扫描为 TIFF 无损图片,存储在远程服务器上。现需将这些 TIFF 批量转为更高效的编码格式,以节省存储空间。 整个流程涉及大量网络 I/O 和 CPU 计算,串行处理效率极低,需要引入并发/并行技术加速。 ### 处理流程:三阶段流水线 ``` 下载(网络下行) → 编码(CPU) → 上传(网络上行) ``` | 阶段 | 处理单元 | 资源限制 | |------|---------|---------| | 下载 | **书**(整本下载后才能开始编码) | 最多 4 个同时连接,每连接带宽 100 Mbps | | 编码 | **页**(每页独立 CPU 任务) | 共 8 个核心,每核速率 50 Mbps | | 上传 | **书**(所有页编码完、打包后才能上传) | 最多 4 个同时连接,每连接带宽 50 Mbps | `注:上述数据可通过参数修改,但实验以上述参数为基准。感兴趣的同学可自行调整探索更多可能。` - 单本书内:下载 → 编码 → 上传 **严格串行**,不可流式(不能边下边编)。 - 多本书间:利用流水线重叠,充分用满网络和 CPU 资源。 ### 基本处理逻辑 对每一本书,按顺序执行以下步骤即串行处理: ``` 1. download(book_id) → 获得 TIFFBook 2. 对 tiff_book.pages 逐页: encode_page(page) → 获得 EncodedPage 3. pack(book_id, encoded_pages) → 获得 EncodedBook 4. upload(encoded_book) → 完成上传 ``` **串行实现(逐本处理)** 是衡量后续并行方案加速效果的基线。请先运行 `baseline.py`,确认环境正确并生成本地基线数据。 --- ## 可考虑的并行方法及关键点 以下仅给出方向性提示,具体方案需自行思考探索。 ### 任务同步 任务从`books_id_list(即所有待处理的书目编号数组)`开始,需考虑如何分配任务以保证不重复、不漏掉。 ### 页级编码并行 一本书内的页面编码是**独立的 CPU 任务**,可以并行执行。注意: - 并行度受 CPU 核心数限制,超出会触发不可恢复的错误。 - 编码并行不能压缩下载和上传的时间,整体加速受 **Amdahl 定律**制约。 ### 网络并发 下载和上传均有连接数上限,超出会抛出可恢复异常。可考虑: - 如何在不超过连接数限制的前提下,让多本书的下载/上传同时进行。 - 如果超出连接数限制,如何等待或重试。 ### 多本书流水线 多本书同时处于流水线的不同阶段时,可以让 I/O 和 CPU 重叠执行: - 书 A 在编码时,书 B 可以同时下载,书 C 可以同时上传。 - 需要考虑各阶段之间的数据传递和同步机制。 - 资源(网络连接、CPU 核心)在流水线中是**全局共享**的,需要在粒度、利用率与复杂性之间权衡。 ### 资源保护 本实验中,不同资源的超限后果是**不同**的: | 资源 | 超限后果 | 应对思路 | |------|---------|---------| | 网络连接(下载/上传) | 抛出 `ConnectionLimitExceeded`异常(可恢复) | 信号量预控 / 捕获后重试 | | CPU 核心(编码) | 程序直接终止(不可恢复) | 必须用信号量等机制严格控制并发数 | **模拟层不做排队/阻塞,所有限制并发策略应自行实现** --- ## `_common.py` 关键实现概述(可结合代码阅读此部分) `_common.py` 是实验的模拟层,封装了网络下载/上传、CPU 编码的全部模拟行为。**无需修改此文件**,只需理解其提供的数据结构、核心函数和资源限制模型。 ### 数据模型 整个流程围绕四个数据结构展开,它们串联起书籍从原始 TIFF 到编码上传的完整生命周期: ``` download() ──→ TIFFBook EncodedBook ──→ upload() ↓ ↑ TIFFPage pack() 组装 ↓ ↑ encode_page() ──→ EncodedPage ``` **TIFFPage**(原始扫描页):包含 `book_id`、`page_id`、`size_MB` 三个字段。由 `get_data()` 从 CSV 构建,通过 `TIFFBook.pages` 访问,作为 `encode_page()` 的输入。 **TIFFBook**(原始书):包含 `book_id` 和 `pages: list[TIFFPage]`,另有一个 `size_MB` 属性返回整书原始总大小。由 `download()` 返回。 **EncodedPage**(编码后的页):结构同 `TIFFPage`,但 `size_MB` 已按压缩比缩小。由 `encode_page()` 返回。 **EncodedBook**(编码后的书):包含 `book_id` 和 `pages: list[EncodedPage]`,同样有 `size_MB` 属性。由 `pack()` 组装,作为 `upload()` 的输入。 > 不需要直接实例化这些类——它们由 `download()`、`encode_page()`、`pack()` 自动创建和返回。理解它们的关系有助于把握数据流向。 ### 核心处理函数 ```python download(book_id: int) -> TIFFBook ``` 从内部字典取出指定书目并模拟网络下载耗时。**受全局下载连接数限制**,超限抛出 `ConnectionLimitExceeded`。 ```python encode_page(page: TIFFPage) -> EncodedPage ``` 模拟 CPU 编码单页的耗时并返回压缩后的结果。**受全局 CPU 核心数限制**,超限时程序直接终止(`sys.exit(1)`),模拟真实的硬件崩溃。 ```python pack(book_id: int, pages: list[EncodedPage]) -> EncodedBook ``` 将一本书全部编码完成的页面组装为 `EncodedBook`,纯内存操作,无耗时模拟。 ```python upload(book: EncodedBook) -> float ``` 模拟网络上传耗时并将结果注册到内部字典供 `verify()` 校验。**受全局上传连接数限制**,超限抛出 `ConnectionLimitExceeded`。重复上传同一 `book_id` 会打印警告。 ### 验证与结果记录 ```python verify(baseline: bool = False) -> dict ``` 检查所有书是否完成编码与上传,打印统计报告(耗时、吞吐量、理论串行耗时、加速比等)。若以 `baseline=True` 调用且验证通过,将本次结果写入 `baseline_result.json` 作为后续加速比计算的参照。返回包含全部统计信息的字典。 ```python record(result: dict) -> None ``` 接收 `verify()` 的返回字典,格式化后写入 `./records/{时间戳}.txt`,便于留存每次实验的记录。 ### 资源限制与异常 模拟层**不做排队和阻塞**——当并发调用数超出资源上限时,行为取决于资源类型: | 资源 | 限制方式 | 超限行为 | 性质 | |------|---------|---------|------| | 下载连接 | 全局 `BoundedSemaphore` | 抛出 `ConnectionLimitExceeded` | 可恢复 | | CPU 核心 | 全局 `BoundedSemaphore` | `print` 错误信息 + `sys.exit(1)` | 不可恢复 | | 上传连接 | 全局 `BoundedSemaphore` | 抛出 `ConnectionLimitExceeded` | 可恢复 | 必须通过信号量等机制**预先控制并发数**,而非依赖模拟层的限流兜底。对应的资源上限常量可直接导入: ```python from _common import CPU_CORES, DOWNLOAD_CONNS, UPLOAD_CONNS ``` ### 待处理数据与参数 ```python from _common import books_id_list # list[int],待处理的书目 ID ``` 所有脚本在 import `_common` 时自动解析以下 CLI 参数,可通过命令行灵活调整实验条件: | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `--limit` | int | 全量 | 限制加载书数,快速测试用 | | `--dataset` | str | `data/books.csv` | 数据集 CSV 路径 | | `--speedup` | float | 100 | 时间压缩因子(越大模拟越快) | | `--download-conns` | int | 4 | 最大同时下载连接数 | | `--upload-conns` | int | 4 | 最大同时上传连接数 | | `--cpu-cores` | int | 8 | CPU 核心数 | | `--bandwidth-dl` | float | 100 | 下载带宽 Mbps | | `--bandwidth-ul` | float | 50 | 上传带宽 Mbps | | `--encode-rate` | float | 50 | 每核编码速率 Mbps | | `--compression-ratio` | float | 0.4 | 编码后大小 / 原始大小 | ```bash python baseline.py --limit 5 --speedup 500 # 5 本书快速验证 python my_solution.py --cpu-cores 16 # 研究核心数对加速比的影响 python my_solution.py --dataset data/large.csv # 切换大数据集测试 ``` ## 拓展实验 以下方向供感兴趣的同学深入探索,可任选其一或多个组合。调整参数时建议使用 `--limit` 缩小规模快速迭代,确认方案后再跑全量。 ### 参数研究 利用 `--cpu-cores`、`--download-conns`、`--upload-conns`、`--bandwidth-dl`、`--bandwidth-ul` 等 CLI 参数,系统性地改变单个变量,观察对加速比的影响。 - **CPU 核心数扫描**:固定其他参数,将 `--cpu-cores` 从 1 逐步增至 16,记录每种方案的加速比,绘制加速比-核心数曲线。观察曲线是否趋于平缓——验证 Amdahl 定律中串行瓶颈对加速上限的约束。 - **网络带宽不对称**:设置下行带宽远大于上行(如 `--bandwidth-dl 500 --bandwidth-ul 50`),或反过来,观察流水线瓶颈如何随带宽比迁移。判断当前参数下哪一阶段是瓶颈,与理论串行耗时中各阶段占比对照。 - **连接数 vs 核心数权衡**:在总"资源"固定(如 conns+cores=12)的前提下,测试不同分配(d=2/u=2/c=8 vs d=4/u=4/c=4)的吞吐量差异,思考 I/O 并行度与 CPU 并行度的最优配比。 - **模拟精度观察**:固定方案和数据,在不同 `--speedup`(10/100/1000/10000)下运行,对比 `压缩因子放大耗时` 与 `理论串行耗时` 的偏差变化。理解 Python 真实开销如何随压缩因子放大,以及为什么 `理论串行耗时` 是更可靠的参照。 ### 算法/策略对比 #### 网络并发控制:信号量预控 vs 异常捕获重试 模拟层在连接数超限时有明确的报错机制。控制并发有两种思路: | 策略 | 实现方式 | 特点 | |------|---------|------| | 信号量预控 | 调用 `download()`/`upload()` 前先 `acquire` 信号量(上限 = 对应连接数) | 主动预防,不会触发异常 | | 捕获后重试 | 直接调用,捕获 `ConnectionLimitExceeded` 后 `time.sleep()` 再重试 | 被动响应,异常处理路径 | 在同一流水线方案中分别实现这两种策略,对比: - 代码复杂度与可读性 - 耗时是否有差异(提示:异常抛出和捕获有 CPU 开销,但在 `SPEEDUP` 模拟下可能不明显) - 哪种策略更容易写出正确的并发控制逻辑?是否存在"明明有空闲连接却因信号量误判而等待"或"重试风暴导致连接数反复超限"的边界情况? #### 任务处理顺序优化 `books_id_list` 中的书在大小、页数上存在差异(可由 `generate_data.py` 生成不同的分布验证)。由于待处理任务是**预先可知的**,可以设计调度策略来最大化流水线利用率: - **按大小排序**:将大书排在前面还是后面?大书编码耗时长,若排在最前会阻塞后续书的流水线入口;排在最后则可能让上游下载提前完成而闲置。分别测试「大→小」「小→大」「随机」三种顺序的吞吐量差异。 - **按页数排序**:页数决定了编码阶段可并行的粒度。页数多的书在编码时能更充分地利用 CPU 核心,页数少的书则可能留出空闲核心给其他书共享(配合全局编码线程池时)。设计与大小排序对比,观察哪种指标(size_MB 还是 page_count)更能预测最优顺序。 - **交错分派**:不按单一字段排序,而是将大书和小书交替排列(如最大-最小-次大-次小),使流水线各阶段负载趋于均衡。对比交错策略与纯排序策略的吞吐量。 - **动态选择**:不预先排序,而是让 worker 从 `books_id_list` 中根据某种策略**动态选取**下一本书(例如"优先选页数最少的"或"优先选大小最接近均值的")。与静态排序方案对比,分析动态选择是否带来额外收益。 - **强化学习调度**:将任务调度建模为马尔可夫决策过程——状态为当前流水线中各阶段的负载(可通过已完成/进行中/待处理的书数量及大小刻画),动作为从待处理队列中选取哪本书进入流水线,奖励为吞吐量(MB/s)或反比于总耗时。可使用 Q-learning 或策略梯度等方法训练一个调度 agent,与启发式排序策略(按大小、页数、交错)对比,观察学习型调度能否超越人工设计的规则。 > `_common.py` 提供了 `get_page_sizes()` 函数(返回二维列表,`[i][j]` 为 `books_id_list[i]` 中第 `j` 页的 `size_MB`,不模拟任何耗时),可用于上述任务排序及强化学习的状态构建。 > 提示:任务顺序优化的效果高度依赖各阶段耗时比例。建议先用 `理论串行耗时` 中的下载/编码/上传三段占比估算瓶颈,再设计排序策略。目标不是让某一阶段最快,而是让瓶颈阶段不闲置。
|
© 2026 MFPIR LAB |
|
|