Mojo 编译期参数与 comptime 元编程
本篇覆盖:编译期参数 vs 运行时参数、参数化函数、参数化 struct、comptime 表达式。这是 Mojo 性能魔法的核心,也是它和 Python 最「不像」的部分。
一、两种「参数」
Mojo 最独特的设计:函数和 struct 有两套参数槽——
mojo
def repeat[count: Int](msg: String):
comptime for i in range(count):
print(msg)| 编译期参数 | 运行时参数 | |
|---|---|---|
| 写在哪 | 方括号 [...] | 圆括号 (...) |
| 何时确定 | 编译期,成为运行时常量 | 运行时传入 |
| 类型 | Int、类型、别名等 | 任意值 |
| 用途 | 特化代码、泛型、常量配置 | 普通数据 |
调用时两边都要给:
mojo
def main():
repeat[3]("Hello") # [3] 是编译期参数, "Hello" 是运行时参数count 是编译期常量,编译器直接为「重复 3 次」生成专门版本——循环可完全展开、可激进优化。不同的 count 值会生成不同的机器码。
记忆法:方括号是「模具参数」,圆括号是「原料」。模具在编译期浇注,原料运行时才倒进去。
二、comptime:编译期执行代码
comptime 关键字让代码在编译期执行:
mojo
def repeat[count: Int](msg: String):
comptime for i in range(count):
print(msg)上面这个 for 循环发生在编译器里——运行时根本没有循环,只有 N 次展开的 print 调用。
再如编译期计算:
mojo
def compile_time_demo[n: Int]() -> Int:
comptime:
var square = n * n
return square # 返回值在编译期就算好了,运行时直接读常量适合的场景:维度计算、查表生成、循环展开、配置推导——所有「每次运行结果都一样」的计算都值得挪到编译期。
三、参数化函数:编译期多态
配合 trait(第五篇),编译期参数就是 Mojo 的泛型:
mojo
def max_of[T: Comparable](a: T, b: T) -> T:
if a > b:
return a
return b
def main():
print(max_of(3, 7)) # Int 版本
print(max_of(2.5, 1.5)) # Float64 版本每个类型组合都会实例化一份专门代码——对比 Python 泛型(运行时检查)和 Java 泛型(类型擦除),Mojo 走的是 C++ 模板路线但没有模板语法的丑陋。
四、参数化 struct
struct 也可以带编译期参数,最典型的例子就是标准库的 SIMD 类型:
mojo
var a = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0)DType.float64:元素类型(编译期参数)4:向量宽度(编译期参数)
也就是说 SIMD[float64, 4] 和 SIMD[float64, 8] 是两个不同的类型,各自的加减乘除都被编译成对应的向量指令。自己也能写:
mojo
struct Matrix[rows: Int, cols: Int, dt: DType]:
var data: List[Scalar[dt]]
def __init__(out self):
self.data = List[Scalar[dt]]()
for _ in range(rows * cols):
self.data.append(Scalar[dt](0))
def shape(self) -> (Int, Int):
return (rows, cols)
def main():
var m = Matrix[3, 4, DType.float64]()
print(m.shape()) # (3, 4)维度在类型里,越界、形状不匹配这类错误直接在编译期拦截。
五、参数能接受的值类型
编译期参数不止 Int:
| 参数类型 | 例子 |
|---|---|
| 整数常量 | [count: Int] |
| 类型(配合 trait 约束) | [T: Stringable] |
| DType 枚举 | [dt: DType] |
| 布尔开关 | [debug: Bool] |
| 别名/函数 | 高阶用法 |
布尔参数常见于开关式特化——同一份代码生成 debug/release 两个版本。
六、什么时候用编译期参数
值得用的信号:
- 值在编译期就确定且影响代码结构(循环边界、维度、类型选择)
- 需要为不同配置生成不同优化版本(SIMD 宽度、精度选择)
- 泛型编程
不值得用的信号:
- 参数需要运行时才能确定(比如用户输入的循环次数)——老老实实用普通参数
- 过度特化会导致编译变慢、二进制膨胀(每个组合都是一份代码)
七、小结
- 方括号 = 编译期参数(模具),圆括号 = 运行时参数(原料)
comptime把计算搬到编译期,运行时零成本- 编译期参数 + trait = 零开销泛型
- 参数化 struct 把「维度/类型」编码进类型系统,编译期拦截形状错误
下一篇:SIMD 向量化编程。