图着色:编译器为什么会把变量当成地图来涂色
写程序时,我们看到的是变量名:
这里会出现一个很漂亮的离散数学模型:图着色。编译器先看每个变量的“活跃区间”,也就是它从什么时候开始有用,到什么时候彻底不用。如果两个变量在同一段时间都还要被使用,它们就不能放在同一个寄存器里。于是编译器把变量画成点,把“不能共用寄存器”的关系画成边。这样一来,分配寄存器就变成了给图上的点涂颜色:相邻的点不能同色,每一种颜色就是一个寄存器。
这个转化的味道很好,因为它没有硬写一堆“如果变量 A 和变量 B 冲突怎么办”的特殊判断,而是把问题换成了一个统一的数据结构。数据结构对了,后面的算法才有地方站住脚。现实里的编译器当然不会天真地求一个完美最优解,因为一般图着色是 NP-hard,追求完美会把编译时间烧掉。它们通常用启发式算法:先挑容易处理的点删掉,最后再倒回来上色;如果颜色不够,就把某些变量 spill 到内存里。
难点在这里:
所以寄存器分配不是一个小优化,它是编译器把数学模型塞进现实机器里的典型例子。抽象得太少,代码会变成补丁堆;抽象得太狠,又会慢到不实用。好实现通常卡在中间:模型干净,策略务实。
思考题:如果一个函数里有 8 个变量,但机器只有 4 个寄存器,是不是一定要把 4 个变量放到内存?
#CS
写程序时,我们看到的是变量名:
a、b、sum、temp。可 CPU 真正能快速使用的,不是这些名字,而是数量很少的寄存器。问题来了:程序里变量可能有几十上百个,寄存器却只有十几个,编译器凭什么决定谁住进寄存器,谁被赶到内存里?这里会出现一个很漂亮的离散数学模型:图着色。编译器先看每个变量的“活跃区间”,也就是它从什么时候开始有用,到什么时候彻底不用。如果两个变量在同一段时间都还要被使用,它们就不能放在同一个寄存器里。于是编译器把变量画成点,把“不能共用寄存器”的关系画成边。这样一来,分配寄存器就变成了给图上的点涂颜色:相邻的点不能同色,每一种颜色就是一个寄存器。
这个转化的味道很好,因为它没有硬写一堆“如果变量 A 和变量 B 冲突怎么办”的特殊判断,而是把问题换成了一个统一的数据结构。数据结构对了,后面的算法才有地方站住脚。现实里的编译器当然不会天真地求一个完美最优解,因为一般图着色是 NP-hard,追求完美会把编译时间烧掉。它们通常用启发式算法:先挑容易处理的点删掉,最后再倒回来上色;如果颜色不够,就把某些变量 spill 到内存里。
难点在这里:
所以寄存器分配不是一个小优化,它是编译器把数学模型塞进现实机器里的典型例子。抽象得太少,代码会变成补丁堆;抽象得太狠,又会慢到不实用。好实现通常卡在中间:模型干净,策略务实。
思考题:如果一个函数里有 8 个变量,但机器只有 4 个寄存器,是不是一定要把 4 个变量放到内存?
#CS