core 教程
本教程展示如何借助 luna_thread 门面从 MoonBit 运行并行整数内核,如何把同样的工作描述为由库验证的计划,以及如何构建并检查工作流图。所有内容都在原生目标上运行。
快速开始
添加模块:
moon add Luna-Flow/luna_thread@0.1.0
在一个为原生目标构建的包中导入门面:
import {
"Luna-Flow/luna_thread",
}
supported_targets = "native"
用两个工作线程对数组求和,每个线程处理至多三个元素的块:
test "quick start" {
let input : FixedArray[Int] = [1, 2, 3, 4, 5, 6]
let total = @luna_thread.execute_reduce_sum_i32(
input,
worker_count=2,
chunk_size=3,
)
inspect(total, content="21")
}
moon test --target native 报告:
Total tests: 1, passed: 1, failed: 0.
日常任务
选择工作线程数和块大小
内核把 个元素分成 个至多 个元素的块,每个块需要一个工作线程,所以先选 ,再取 。两者都不能超过 。
fn chunked_sum(input : FixedArray[Int], chunk_size : Int) -> Int {
let n = input.length()
let workers = (n + chunk_size - 1) / chunk_size
@luna_thread.execute_reduce_sum_i32(input, worker_count=workers, chunk_size~)
}
test "choose the parameters" {
let input : FixedArray[Int] = [10, 20, 30, 40, 50, 60, 70]
inspect(chunked_sum(input, 3), content="280")
inspect(chunked_sum(input, 7), content="280")
}
把元素翻倍并处理溢出
execute_map_i32 把每个元素翻倍,并把溢出作为错误报告,而不是回绕:
test "map with overflow" {
let small : FixedArray[Int] = [1, -2, 3, 4]
debug_inspect(
@luna_thread.execute_map_i32(small, worker_count=2, chunk_size=2),
content="Ok(<FixedArray: [2, -4, 6, 8]>)",
)
let large : FixedArray[Int] = [1, 2147483647]
debug_inspect(
@luna_thread.execute_map_i32(large, worker_count=2, chunk_size=1),
content="Err(Overflow)",
)
}
计算前缀和
execute_scan_sum_i32 返回累计总和:
test "prefix sums" {
let deposits : FixedArray[Int] = [100, -30, 50, 20, -10]
match @luna_thread.execute_scan_sum_i32(deposits, worker_count=2, chunk_size=3) {
Ok(balance) => debug_inspect(balance, content="<FixedArray: [100, 70, 120, 140, 130]>")
Err(error) => fail("scan failed: \{Repr(error)}")
}
}
把工作描述为计划并验证它
计划记录应该运行什么,但不运行它。验证会告诉你 v1 运行时是否支持它:
test "plans" {
let policy = @luna_thread.make_policy(worker_count=2, chunk_size=3).unwrap()
let plan = @luna_thread.reduce(
"total",
@luna_thread.i32_type(),
6,
@luna_thread.sum_reduction(),
policy~,
)
assert_true(@luna_thread.is_ready(plan))
let doubles = @luna_thread.map("halve", @luna_thread.f64_type(), 6, policy~)
debug_inspect(@luna_thread.validate(doubles), content="[UnsupportedValueType]")
}
构建并检查工作流
工作流是由任务组成的图。这个工作流先派生,再计算上面的计划,最后汇合,并用边固定顺序:
test "workflow" {
let policy = @luna_thread.make_policy(worker_count=2, chunk_size=3).unwrap()
let plan = @luna_thread.reduce(
"total",
@luna_thread.i32_type(),
6,
@luna_thread.sum_reduction(),
policy~,
)
let graph = @luna_thread.workflow("sum-job", policy~)
.add_node(@luna_thread.spawn_task(1, "spawn"))
.add_node(@luna_thread.compute_task(2, "sum", plan))
.add_node(@luna_thread.join_task(3, "join"))
.add_edge(@workflow.Edge::new(1, 2, @workflow.control_dependency()))
.add_edge(@workflow.Edge::new(2, 3, @workflow.control_dependency()))
assert_true(@luna_thread.workflow_is_ready(graph))
let submission = @luna_thread.submit_workflow(graph)
inspect(submission.accepted(), content="true")
}
为了使用 Edge,导入中还需要 "Luna-Flow/luna_thread/workflow"。submit_workflow 验证并记录提交;它不会运行图。
进阶
要在真实线程上运行工作流的同步协议,请用 submit_workflow_async 提交,然后调用 wait_workflow 和 drop_workflow。由于 C 桥接层的一个已知缺陷,异步路径是实验性的;使用前请阅读 core API 中的警告。
对于其他元素类型和内核,backend/native 暴露了原始的 C 入口,包括 Int64 以及最小值和最大值归约;参见原生后端教程。若要更精细地控制计划和图,请直接使用 plan 和 workflow 包:plan 教程和 workflow 教程逐字段地构建计划和图。只描述工作的包可以导入这两个包,并在所有目标上构建。
常见陷阱
- 默认值
worker_count=1和chunk_size=1只适用于单元素输入。对于更长的输入,内核返回Err(InvalidArgument),execute_reduce_sum_i32返回0。 execute_reduce_sum_i32在和为零以及任何失败时都返回0。需要区分二者时,请自行检查输入和参数,或者改用扫描并取最后一个元素。- 由于部分和会被检查,
Int求和是否溢出可能取决于分块方式:[-1, 0, 2147483647, 1]在chunk_size=4时能求和,在chunk_size=2时却失败。 is_ready不检查“每块一个工作线程”的条件,因此就绪的计划仍可能被内核拒绝。javascript_policy()在 v1 中会中止,make_policy会拒绝 JavaScript 后端和异步模式。Workflow::add_node和其他构建方法会就地修改工作流。- 门面只能在原生目标上构建。导入它的包需要
supported_targets = "native",或者只在原生目标上构建。
下一步
core API 列出了每个门面函数及其精确语义。core 设计解释了为什么门面是一组带默认值的自由函数,原生后端设计解释了内核和工作流调度器如何使用线程和内存。架构指南展示了各个包和 C 运行时如何组合在一起。