backend/native 教程

本教程展示如何直接调用原生后端:运行带类型的内核,通过原始外部函数使用 Int64 内核以及最小值和最大值内核,把计划转换为原生请求,并在 C 调度器上运行工作流。

快速开始

在一个为原生目标构建的包中导入该后端:

import {
  "Luna-Flow/luna_thread/backend/native",
}

supported_targets = "native"

用三个工作线程、块大小为二来计算前缀和:

test "native quick start" {
  let input : FixedArray[Int] = [2, 4, 6, 8, 10, 12]
  debug_inspect(
    @native.execute_scan_sum_i32(input, 3, 2),
    content="Ok(<FixedArray: [2, 6, 12, 20, 30, 42]>)",
  )
}

日常任务

求 Int64 数据的最小值和最大值

原始函数显式接受长度,并直接返回结果:

test "min and max" {
  let samples : FixedArray[Int64] = [12L, -7L, 40L, 3L, 0L, 25L]
  let n = samples.length()
  assert_eq(@native.ffi_execute_reduce_min_i64(samples, n, 2, 3), -7L)
  assert_eq(@native.ffi_execute_reduce_max_i64(samples, n, 2, 3), 40L)
}

写入你自己的输出数组

映射和扫描写入你提供的数组,并返回状态码,成功时为 0:

test "output array" {
  let input : FixedArray[Int64] = [1L, 2L, 3L, 4L]
  let output : FixedArray[Int64] = FixedArray::make(4, 0L)
  let status = @native.ffi_execute_map_i64(input, 4, 2, 2, output)
  assert_eq(status, 0)
  debug_inspect(output, content="<FixedArray: [2, 4, 6, 8]>")
}

把计划转换为请求

*_from_plan 函数检查计划,并把它转换为后端的带类型请求:

test "request from plan" {
  let policy = @shared.make_execution_policy(worker_count=2, chunk_size=4).unwrap()
  let plan = @plan.scan("prefix", @plan.i64_type(), 8, policy~)
  guard @native.scan_request_from_plan(plan) is Ok(request) else {
    fail("expected a request")
  }
  assert_true(@native.scan_request_is_valid(request))
  assert_eq(request.element_count, 8)
  assert_eq(@native.scan_request_value_type(request), 1)
}

运行工作流

submit_workflow_async 在工作线程上启动图;wait_workflow 阻塞直到它完成。由于 backend/native API 中描述的已知桥接缺陷,这个示例没有编译进文档测试:

let policy = @shared.make_execution_policy(worker_count=2).unwrap()
let graph = @workflow.Workflow::new("fork-join", policy~)
  .add_node(@workflow.spawn_node(1, "spawn"))
  .add_node(@workflow.join_node(2, "join"))
  .add_edge(@workflow.Edge::new(1, 2, @workflow.control_dependency()))
guard @native.submit_workflow_async(graph) is Ok(handle) else { return }
let result = @native.wait_workflow(handle)
@native.drop_workflow(handle)
// result.state is Completed, result.completed_nodes is 2

进阶

内核都接受相同的三个数 nn、ww 和 cc;原生后端设计推导了它们如何划分输入、为什么成功时结果与 ww 和 cc 无关,以及何时检测到溢出。若要在 MoonBit 之外以 OpenMP 链接 C 运行时,请按架构指南所述用 CMake 构建 native/。

常见陷阱

  • 每个内核都需要 w≥⌈n/c⌉w \ge \lceil n / c \rceil、w≤nw \le n 和 c≤nc \le n;否则以 InvalidArgument 失败(归约则返回 0)。
  • 原始函数信任你传入的 length。传入的值大于数组实际长度时,会读到数组末尾之外。
  • moon 构建没有启用 OpenMP:即使 supports_openmp() 返回 true,内核也在调用线程上运行。
  • 由计划构建的请求带有空缓冲区;目前没有任何东西执行它们。
  • 若工作流的节点阻塞后从未被唤醒,它就永远不会结束,wait_workflow 也不会返回。

下一步

backend/native API 列出了每个函数,包括原始函数。backend/native 设计描述了线程和内存模型。core 教程通过门面展示了同样的内核。