十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

oneTBB task_group 接口实战:基于 Sudoku 状态空间搜索示例的 OR 并行编程指南

oneTBB task_group 接口实战:基于 Sudoku 状态空间搜索示例的 OR 并行编程指南 oneTBB task_group 接口实战基于 Sudoku 状态空间搜索示例的 OR 并行编程指南【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本指南以当前仓库 third-party/tbb/examples/task_group/README.md 为纲围绕其唯一示例sudoku数独全解搜索程序展开。读者将掌握 oneAPI Threading Building BlocksoneTBBtask_group接口的完整用法——包括如何通过g.run()以 OR 并行方式并行化状态空间搜索、如何用g.cancel()在找到首个解时取消整组任务、如何用global_control限制并发度以及如何借助 CMake 预置目标完成性能测量与基准数据收集。文中所涉代码与配置均可在仓库对应路径下直接查看、构建与运行。示例一览task_group 目录下有什么仓库在third-party/tbb/examples/task_group/目录下集中存放演示task_group接口的代码样例。顶层 README 给出了一张清单Code sample nameDescriptionsudokuCompute all solutions for a Sudoku board.目录结构为third-party/tbb/examples/task_group/README.md示例清单与简介third-party/tbb/examples/task_group/sudoku/README.mdsudoku 示例的构建、运行与参数说明third-party/tbb/examples/task_group/sudoku/sudoku.cpp示例完整源码third-party/tbb/examples/task_group/sudoku/CMakeLists.txt构建脚本与预置执行/基准目标input1~input4四个不同解数量的输入棋盘。sudoku 示例采用直观的状态空间搜索state-space search算法天然呈现OR 并行OR-parallelism特征对某个待定格子尝试不同候选值即构成互斥的分支各分支可以独立并行探索互不依赖、互不冲突。示例同时支持两种终止条件——求出全部解或找到第一个解即停。README 明确指出该示例的教学重点是task_group接口的使用。并行化前的准备棋盘的数据表示与候选值计算在进入task_group之前先理解 sudoku 示例的数据结构这决定了任务划分方式。源码 sudoku.cpp 用两个常量与一个结构体描述棋盘const unsigned BOARD_SIZE 81; // 9x9 棋盘总格数 const unsigned BOARD_DIM 9; // 每行/列维度 typedef struct { unsigned short solved_element; // 已确定数字0 表示未定 unsigned potential_set; // 候选值集合按位掩码表示 } board_element;关键设计是potential_set使用位掩码第k-1位为 1 表示数字k1~9可作为该格候选。calculate_potentials()对每个空格检查同行、同列、同 3x3 宫in_row/in_col/in_block后累加候选位if (!in_row(b, row, col, potential) !in_col(b, row, col, potential) !in_block(b, row, col, potential)) b[i].potential_set | 1 (potential - 1);examine_potentials()则扫描候选位掩码如果某个空格只剩唯一候选掩码恰为 2 的幂1、2、4、8、16、32、64、128、256分别对应数字 1~9就立即填入该数字实现“确定性推进”并返回progress true从而减少不必要的任务分支。核心并行结构partial_solve 与 OR 并行分支task_group的核心用法集中在递归函数partial_solve()中。先看其整体逻辑sudoku.cpp终止判断若棋盘已全部填满fixed_board说明得到一个解若find_one为真则调用g.cancel()取消整个任务组否则累计nSolsstd::atomicunsigned并在verbose模式下打印第一个解确定性化简calculate_potentialsexamine_potentials尽量填满单候选格若填入了新数字则继续递归partial_solve(g, b, first_potential_set)分支生成若无可再填的单候选格则挑第一个未定格子枚举其候选值为每个候选值创建分支任务for (unsigned short potential 1; potential BOARD_DIM; potential) { if (1 (potential - 1) b[first_potential_set].potential_set) { g.run([g, b /*make a copy of the board*/, first_potential_set, potential]() { // as task_group treat passed in functor as const - const_cast is needed // to allow modification of the copy auto new_board const_caststd::vectorboard_element(b); new_board[first_potential_set].solved_element potential; partial_solve(g, new_board, first_potential_set); }); } }这里有三个值得深入讲解的task_group使用要点要点一按值捕获制造任务隔离。Lambda 捕获列表中的b是棋盘的拷贝代码注释明确写出make a copy of the board。每个分支任务操作自己的棋盘副本因此各个 OR 分支之间完全无共享写状态这正是 OR 并行能够安全并发的根本前提——不存在数据竞争也就不需要加锁。要点二const_cast 的由来。注释揭示了 oneTBB 的行为task_group会把传入的仿函数当作const处理因此 lambda 体内部拿到的捕获对象是 const 的示例通过const_cast去掉b的 const 限定以修改副本。这是在 oneTBB 版本约束下的兼容写法阅读源码时需要注意这一点。要点三任务组内再递归。每个g.run()的任务体内部又可能再次调用partial_solve进而再g.run()出新的子任务——task_group天然支持在任务体内继续往同一个组提交任务从而形成任务树。任务树的所有叶子任务都归同一个task_group g管理由 oneTBB 调度器在工作线程间负载均衡地执行。从驱动侧理解 task_group 的完整生命周期solve()函数展示了task_group与并发度控制的配合sudoku.cppunsigned solve(int p, utility::measurements solve_measurements) { oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, p); nSols 0; std::vectorboard_element start_board(BOARD_SIZE); init_board(start_board, init_values); oneapi::tbb::task_group g; solve_measurements.start(); partial_solve(g, start_board, 0); g.wait(); ... return nSols; }oneapi::tbb::global_control以max_allowed_parallelism动态限制本次求解使用的线程数上限为p配合-DTBB_PREVIEW_GLOBAL_CONTROL等编译开关oneTBB 运行时读取该值约束并行度创建task_group g后先调用partial_solve(g, start_board, 0)提交根任务随后g.wait()阻塞等待该组内所有任务完成或find-one模式下被取消。在 oneTBB 头文件 third-party/tbb/include/oneapi/tbb/task_group.h 中可以看到task_group的 API 骨架templatetypename F void run(F f)把f封装为任务并投递给调度器内部走d1::spawn(*prepare_task(...), context())void cancel()对该组的task_group_context发起取消内部调用context().cancel_group_execution()后续尚未执行的任务将被跳过run_and_wait(F f)运行f并立即等待其完成返回task_group_statuscomplete/canceled等defer(F f)返回task_handle可延迟到后续run()提交用于表达任务间依赖构造函数默认以task_group_context::concurrent_wait特质创建上下文允许任务体内并发执行wait()。sudoku 示例使用的正是其中最核心的run()wait()cancel()三件套覆盖了典型的“生成-并行-聚合/取消”模式。构建示例CMake 流程sudoku 示例使用 CMake 构建其 CMakeLists.txt 关键内容如下cmake_minimum_required(VERSION 3.5.0...3.31.3) project(sudoku CXX) include(../../common/cmake/common.cmake) set_common_project_settings(tbb) add_executable(sudoku sudoku.cpp) target_link_libraries(sudoku TBB::tbb Threads::Threads)构建命令见 sudoku/README.mdcmake path_to_example cmake --build .其中path_to_example即本示例目录例如third-party/tbb/examples/task_group/sudoku。set_common_project_settings(tbb)宏定义于 third-party/tbb/examples/common/cmake/common.cmake会完成把examples/根目录加入头文件搜索路径、通过find_package(TBB REQUIRED COMPONENTS tbb)定位已安装的 oneTBB 库、并默认设置 C11 及以上标准CMAKE_CXX_STANDARD 11且关闭编译器扩展。链接目标除TBB::tbb外还包含Threads::Threads。运行方式与命令行参数详解示例 README 给出完整用法sudoku [n-of-threadsvalue] [filenamevalue] [verbose] [silent] [find-one] [-h] [n-of-threads [filename]]各参数含义如下表综合 sudoku/README.md 与 sudoku.cpp 的参数解析代码整理参数说明-h打印命令行选项帮助由utility::parse_cli_arguments隐式提供n-of-threads使用的线程数支持low[:high]区间形式low与可选的high为非负整数或auto表示平台默认线程数filename输入文件路径缺省时使用源码内置的默认棋盘init_values静态数组n-of-repeats重复求解同一棋盘以验证解的次数大于 1 时计算各次耗时的相对误差verbose打印第一个解silent除耗时与相对误差外不输出任何内容且会强制关闭verbosefind-one找到第一个解后即停止源码中位置参数与开关的注册方式为utility::parse_cli_arguments( argc, argv, utility::cli_argument_pack() .positional_arg(threads, n-of-threads, utility::thread_number_range_desc) .positional_arg(filename, filename, input filename) .positional_arg(repeats, n-of-repeats, ...) .arg(verbose, verbose, prints the first solution) .arg(silent, silent, no output except elapsed time) .arg(find_one, find-one, stops after finding first solution\n));main()会遍历线程区间threads.first .. threads.last按threads.step递增逐档测速输出形如Sudoku: Time to find all N solutions on P threads: X.XXXXXX seconds.的结果当repeats 1时还会追加Relative_Err : ...相对误差报告。输入文件与预置执行目标示例目录自带四个输入棋盘sudoku/README.md输入文件特点input1解数量适中的样例input2解数量较少的样例input3解数量较多的样例input4解数量非常多的样例每个文件第一行为 81 个 0~9 整数0 表示空格后续为人类可读的 9x9 棋盘展示。例如 input11 0 0 9 0 0 0 8 0 0 8 0 2 0 0 0 0 0 ... 1 0 0 9 0 0 0 8 0 0 8 0 2 0 0 0 0 0 0 0 5 0 0 0 7 0 0 ...CMake 为示例定义了 4 个预置目标见 sudoku/CMakeLists.txt其底层参数如下Make 目标实际命令参数用途run_sudoku4 srcdir/input1 verbose用 4 线程跑input1并打印第一个解perf_run_sudokuauto srcdir/input1 silent以auto线程数、静默模式测量 oneTBB 性能benchmark_sudokufilenamesrcdir/input4 n-of-repeats3用解数量极大的input4重复 3 次测性能并报告相对误差benchmark_sudoku_data同上同上但结果写入benchmark_sudoku_data.csv这些目标由 common.cmake 中的add_execution_target/add_benchmark_target宏生成。add_benchmark_target会查找系统gawk把程序输出通过管道交给 benchmark_sudoku_data.awk 解析awk 脚本用正则匹配Sudoku: Time to find ... on N threads: ... seconds.行累加各线程档位的耗时与次数遇到Relative_Err行时输出一行 CSV 记录CSV 表头为infile,repetitions,threads,avg_duration,rel_error注意make benchmark_sudoku_data依赖系统安装gawk若缺失CMake 会打印警告并跳过该目标的生成见 common.cmake 中的find_program(GAWK gawk)逻辑。阅读与扩展建议想对照 API 细节可阅读 third-party/tbb/include/oneapi/tbb/task_group.h 中task_group/task_group_base类的定义run、wait、cancel、run_and_wait、defer以及底层task_group_context的取消状态机my_cancellation_requested原子标志、cancel_group_execution导出函数想观察 OR 并行任务划分可对照 sudoku.cpp 中partial_solve的g.run分支生成代码体会“按值捕获副本 任务内递归提交”的组合模式想复现性能测量可按上文 CMake 流程构建后执行make perf_run_sudoku或make benchmark_sudoku_data观察不同线程数下的耗时与相对误差。需要特别说明的是本示例以教学task_group接口为核心目标因此优先展示任务划分的直观性与代码可读性并未针对数独求解做剪枝深度优化若将其用于实际求解场景仍可在候选格选择MRV 启发式与去重策略上进一步改进并行框架本身保持不变。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表