Skip to content
当前页大纲

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 向量化编程。

MIT License.