接入现有项目
从 Node.js、Python 或 Rust 调用 CK 内核。
CK 适合放进现有应用中的一小段计算。你的程序仍然可以用 Node.js、Python 或 Rust 编写;当测量发现某个计算步骤占用不少时间时,再考虑把这段计算单独交给 CK。
为什么不直接用 Rust 或 C++?#
Rust 和 C++ 都适合构建完整应用、系统软件,也有成熟的库和开发工具。CK 的职责更专注:描述一段计算,编译成目标环境可以使用的形式,再让其他程序调用。
尝试 CK 不代表要重写整个应用。页面、文件、数据库、网络请求仍由原来的程序负责;你可以只把一段计算单独拿出来,再用真实工作负载比较结果。CK 不会自动比 Rust 或 C++ 快,实际表现取决于算法、数据和运行设备。
什么时候值得试 CK?#
如果性能分析发现,一段规模较小的数值计算占用了明显时间,而且它可以通过清楚的函数接口接收输入、返回结果,就值得做一个小实验。例如重复评分、数值变换、模拟或图像处理中的某个计算步骤。
如果现有代码已经够快,大部分时间都在等文件或网络,或者它调用的库已经高效地完成了计算,就先保留原实现。从一个函数开始,并测量完整调用过程,包含跨语言调用本身的开销。
把一段计算变成可调用函数#
在顶层函数前写 export,即可让目标输出提供这个函数。下面的例子接收两个有符号 64 位整数并返回它们的和:
export fn add_i64(a: i64, b: i64) -> i64 {
return a + b;
}
把它保存为 kernel.ck,先检查文件:
ckc check kernel.ck
export 让函数可以出现在支持的输出中,但还没有决定现有程序如何加载它。可以从下面的方式开始:
| 现有项目 | 入门方式 | 需要了解的事 |
|---|---|---|
| Node.js | WebAssembly | 可以用 Node.js 自带的 WebAssembly API 调用标量函数。 |
| Python | Native 动态库和 ctypes |
可以通过 C ABI 尝试接入;仓库目前没有 Python 绑定测试。 |
| Rust | Native 动态库或静态库和 C FFI | CK 会生成 C ABI 头文件;仓库有内部库调用测试,但没有独立 Cargo 示例。 |
Node.js:调用 WebAssembly 标量函数#
生成 WebAssembly 模块:
ckc emit-wasm kernel.ck --out kernel.wasm
在 Node.js 项目中,把以下内容保存为 host.mjs,并与 kernel.wasm 放在一起:
import { readFile } from 'node:fs/promises'
const bytes = await readFile(new URL('./kernel.wasm', import.meta.url))
const { instance } = await WebAssembly.instantiate(bytes)
console.log(instance.exports.add_i64(20n, 22n).toString())
运行 node host.mjs,会看到 42。WebAssembly 的 i64 在 JavaScript 调用边界上使用 BigInt,所以输入写成 20n 和 22n。
这个标量示例对应仓库测试过的 Node/WebAssembly 导出形式。如果传递指针或切片,宿主程序需要自行分配和管理 WebAssembly 内存,并传入字节地址;CK 不提供内存分配器。WebAssembly 目前只支持 unchecked 溢出和边界模式。传递大量数据前,请先阅读输出方式。
Python:通过 ctypes 使用 Native C ABI#
Python 可以通过标准库 ctypes 加载 Native 动态库。下面的 Native 示例需要包含 Native 支持的发行版 ckc,可从 CalcKernel 发布页获取。请在要运行程序的机器上,为当前平台构建库;CK 文件中应包含前面的 add_i64 函数:
ckc build kernel.ck --kind dynamic --out libkernel
编译器会生成对应平台的动态库,以及放在旁边的 C 头文件。使用上面的输出名称时,macOS 生成 libkernel.dylib,Linux 生成 libkernel.so,Windows 生成 kernel.dll;头文件为 libkernel.h。把下面代码保存为同一文件夹中的 host.py,并在该目录运行 python host.py:
import ctypes
import sys
from pathlib import Path
if sys.platform == "darwin":
library_name = "libkernel.dylib"
elif sys.platform == "win32":
library_name = "kernel.dll"
else:
library_name = "libkernel.so"
library_path = Path(__file__).resolve().parent / library_name
library = ctypes.CDLL(str(library_path))
add_i64 = library.add_i64
add_i64.argtypes = [ctypes.c_int64, ctypes.c_int64]
add_i64.restype = ctypes.c_int64
print(add_i64(20, 22))
运行后会打印 42。ctypes 类型与生成头文件中的 int64_t 参数和返回值对应。如果你更改了输出名称或文件夹,请把库路径改为编译器实际生成的位置。
这是一条可尝试的接入路径,不是 CalcKernel 官方 Python 绑定:仓库目前没有提供或测试类似 pip 包的封装,也没有测试 ctypes 调用。建议先从标量函数开始,并在每个目标平台核对库路径、导出名称和类型。CK 不管理宿主程序传入的内存;若传递缓冲区,Python 端必须保证调用期间它一直有效。传递指针或切片前,请阅读内存与安全。
Rust:调用生成的 C ABI#
Rust 可以链接 Native 静态库,并在 extern "C" 边界声明导出函数。在与 Rust 程序相同的机器和目标平台上构建库;CK 文件中应包含前面的 add_i64 函数:
ckc build kernel.ck --kind static --out libkernel_static
macOS 和 Linux 会生成 libkernel_static.a 与 libkernel_static.h。Windows 会生成 kernel_static.lib,头文件仍为 libkernel_static.h。把下面代码保存为同一文件夹中的 host.rs:
#[link(name = "kernel_static", kind = "static")]
unsafe extern "C" {
fn add_i64(a: i64, b: i64) -> i64;
}
fn main() {
let answer = unsafe { add_i64(20, 22) };
println!("{answer}");
}
编译并运行:
rustc host.rs -L native=. -o host
./host
程序会打印 42。生成的 libkernel_static.h 是导出名称、C 类型和调用细节的依据,Rust 声明必须与它完全一致;通过外部函数接口调用属于 unsafe。Windows 请使用匹配的 Rust 目标和链接器链接 kernel_static.lib,生成的程序使用 .exe。仓库内部验证了 Native 库符号加载,但没有现成的 Cargo 封装。这个静态链接示例不需要处理各平台不同的动态库搜索路径。
第一次接入时,建议先使用标量输入和输出。如果要传递数组或指针,Rust 端必须提供大小正确、对齐正确,并且在调用期间持续有效的内存。更多信息见 Native 与 C ABI 和内存与安全。
先把调用边界做小#
从一个导出函数和简单数值开始。检查 .ck 文件,构建或生成选定的目标,再从一个小型宿主程序调用它。然后用有代表性的输入测量,并与原应用中的相同计算比较。
Native 和 C 头文件定义了实际的外部函数接口。默认 unchecked 模式下,简单标量函数使用直接的 C 参数和返回值。如果启用了溢出或边界检查,生成的 Native ABI 可能包含状态码和结果输出参数;不要猜测签名,应以生成的头文件为准。指针和切片的规则见内存与安全。
链接到仓库的完整参考文档以 main 分支为准,可能包含尚未进入最新下载版本的功能。