知乎热榜 ( ) • 2024-04-22 14:37
酱紫君的回答

其实非常常见, 在高层语言源代码上做的优化其实就是宏(Macro)

可能在你印象中, 宏就是少打两个字那么简单, 压根称不上什么优化.

但是其实宏的花活非常的多, 做出的优化可以非常的强大.

你见过的语言做不到是因为这些语言太弱了, 低序列的元编程还用不到这个特性.


元编程序列中所谓的宏, 说白了就是一个 AST -> AST 的编译期函数.

宏的输入是高级语言合法的源代码, 输出同样也是某个高级语言的合法源代码.

低序列

最低级的用法就是根据表达式, 预分配内存什么的, 比如 rust 的 vec![], 虽然每个只有一点点加速, 但是积累起来也算个小优化了.

中序列

中级用法就是根据编译期类型手动派发需要的单态化函数, 不然你只能运行时装箱并且用反射做, 效率极低. C# 现在就在大规模应用 ISourceGenerator, 替换原来反射才能实现的功能.

还有就是生成类型安全的 print, 比如 rust 的 format_args! 这种, 避免在编译器上开洞, 引入 varargs 之类难以优化的东西.

还有就是承担一部分重载的功能, 实现编译期的模式匹配, 别整天 *args**kws 了, 编译器都疯了.

高序列

高级用法就是跑一个小的 DSL, DSL 语义更明确, 更加形式化, 因此比起 GPL 能做更多的优化, 用宏翻译一下, 代码简洁又高效.

这方面还是看 rust, 高性能 parser, sql engine, deserialization 等等全部用到了这个特性.

另一方面不光能从 DSL 变成高级语言, 也能从高级语言发射指令给别的语言, 比如 asm, 或者 cuda 代码.

这些东西你要是交给 IR 做当然也可以, 但是基本上得魔改编译器, 或者给 LLVM 写插件, 远没有宏灵活.

宏不需要任何魔法, 宏是一种语言的内在机制.

序列零

终级用法就厉害了, 叫做偏计算(Partial Evaluation), 简单的说加入了计算能力, 可以把已知的部分先算了, 然后运行时算剩下的残程式(Residual Program).

注意: 正常的宏是没有计算值的能力(Evaluation)的, 你没法知道表达式结果的类型, 只是一种单纯的节点匹配(Match/Dispatch).

这个东西可以做到值级别的特化, 比如 pow(1, y), pow(2, y), pow(3, y) ... pow(x, y) 全部生成不一样的最优字节码.

面对这种情况 inline 和 monomorphic 就是个弟弟.

要知道很多泛型函数对于类型是没有特异性的, 但是对于单点的值是有特异性的, 此时存在一个极度优化的版本, 但无论是 rust 还是 cpp 都没法做的这么精细, 而 zig 可以.

在这样的语言里是没有什么 AOT 和 JIT 的区别的, 也没有 compile-time 和 runtime 的区别, 只有 binding-time.

每次获得新的信息就是一次 binding-time (绑定时), 然后会生成一个新的 world age(世界纪元)

而只要还有新的信息 world age 就会不断延伸!!!

不要停下优化啊啊啊!!!!!!

world age 一步又一步, 组成了 Multi-Stage Programming 的阶梯, 这就是元编程途径的终点.

旧日

但是 MSP 也只是区区序列零罢了, 如果你到这一步还没疯, 继续突破就能见到旧日支柱之 Futamura Projection (扶她映射) 啦.