Programming Language Roam: Load-Time Calculation

「加载期计算」,是指: 在代码加载时执行运算或者这种能力 和 编译期计算 相比,加载期计算可能适用范围更广 场景 加载期计算主要适用于以下的场景: 程序运行时需要的某些信息,如环境变量等,无法在编译期间确定 这些信息在程序启动后几乎不会发生变化 程序运行时需要根据不同的环境信息,执行不同的运算 比较典型的场景就是跨平台应用,以及使用静态配置的应用,这些应用在真正运行前是无法获得目标平台、或者目标配置的 值。而运行后,要么对这些信息进行缓存以便反复使用,要么就是每次使用这些信息时,都重新进行一次读取/查询。 而如果程序支持「加载期计算」,则可以在代码加载时,读取相关信息,然后直接将信息写入到调用的代码处,甚至,可以根据 信息的值来选择性生成不同的处理代码。 这样,对于程序而言,这些信息就变成了静态的常量了,且无用的代码分支也会被裁剪掉,这样就减少了程序在缓存、调用、 分支预测上的开销了 Load-Time Hook/Callback 部分语言,比如 Erlang、C#(Unity) 等支持在模块、类、程序集加载时,执行某个函数,这种行为可以看作是代码加载时的一个 回调,或者说是一种受限的「加载期计算」,因为这样的计算,执行的都是编译期已经生成好的代码,无法通过在加载期间执行的运算来影响到原有的代码逻辑。 C# (.Net) 可以在加载时,找到对应属性的程序集、类、方法、乃至字段等,然后动态修改、生成代码。 这种操作,可以算是在代码加载期间执行的 JIT 操作,而「加载期计算」更多的是强调「计算」或者说 「求值」, 但总的来说,区别不大,可以看作是一回事。 示例,零开销配置 这里做一个简单的演示,使用 message 文本文件做为配置文件,里面的内容为: Hello, World! 定义函数 get-message ,直接读取整个文本文件,当作是配置读取操作: (defun get-message () (uiop:read-file-string "~/message")) 定义函数 test , 直接使用 get-message 模拟读取配置操作 (defun test () (let ((msg (get-message))) msg)) 为了防止编译器优化,这里写的很啰嗦,第三行的 msg 实际上可以看作是具体的配置操作过程,只是这里简单的用返回这个行为 来模拟 定义一个函数 test2 , 使用加载期计算,在代码加载时,求出 get-message 的值,然后直接写入到 test2 的代码内...

March 20, 2022 · firest

Programming Language Roam: 编译期计算

最近在工作中的一些场景,让我想到了「编译期计算」这个概念,然而一直没有时间整理、复习下相关概念。 什么是编译期计算 「编译期计算」是指: 可以在编译期间执行的的运算或者这种能力 这并不是什么新的概念,最古老的语言之一的 C 的 宏 就具有很弱的编译期计算能力,而之后的编译语言中,凡是支持 元编程的,多多少少都能支持编译期间预算。 而另外一方面,编译期计算也是一种常见的编译优化手段,高优化的编译器会尽可能的,对在编译期间可以求值的计算过程进行 求值,然后内联其计算结果,比如:如果一个循环内所有变量都是常量,在 -O2 和 -O3 的情况下,GCC 生成的代码多半只有 循环结果,而没有循环的过程。 但是,相对而言,大部分编程语言所支持的 编译期计算 和 Lisp 比起来,都很弱 做为最古老的编程语言之一,Lisp 发明了很多 超时代 的特性,最出名的莫过于 垃圾回收(GC) 了,但是 Lisp 对 计算的控制粒度和灵活度,目前也鲜有对手。 Common Lisp 和 编译期计算 C++11 引入了一个关键字 constexpr,用来声明一个函数或者变量,在编译期是可能进行求值的,相比 宏 和 模板 , 这是一个更加强大的编译期计算的能力。 不过,这种能力却是 30 多年前的 Common Lisp 里面的内建功能,这里用一个求和函数进行演示 (defpackage ct (:use :cl) (:export :test1 :test2)) (in-package ct) ;;; 声明一个求和函数 sum ;;; 设定函数 sum 具有在编译期和执行时进行运算的能力 ;;; 默认情况下函数只能在执行时(execute)才能进行计算 (eval-when (:compile-toplevel :execute) (defun sum (&rest args) (apply #'+ args))) ;;; 声明函数 test1,使用求和函数求值 (defun test1 () (let ((x (sum 1 2 3 ))) x)) ;;; 声明函数 test2,使用语言自带的加法运算求值 (defun test2 () (let ((x (+ 1 2 3))) x)) 然后在 REPL 中执行编译和加载...

March 18, 2022 · firest