传统的过程內分析,采用很保守的策略,假设所有对过程可见的变量都可能改变,并默认所有可能的操作都有副作用,因此过程內分析和优化非常简单。函数调用对于优化策略来说,隐藏了副作用的信息,虽然inline能够将副作用信息暴露给调用者,但这会大大增加代码量并降低指令局部性,而且并非所有的函数都能inline(如,对于不可预测副作用的函数)。过程间分析(InterProcedural Analysis)就应运而生,被用来作程序调用分析,能够处理inline不能处理的问题。删除不必要的函数调用,做指针和常数传播等。
IPA 提供了完全不同的策略,先分析过程內的信息,如别名和依赖关系,然后将这些信息在调用图(根据过程间的调用关系得到的图)中传播,然后就可以利用这些信息决定将函数特殊化,移除函数中的某些不可能经过的路径或者内联函数。通过这些分析,能降低编译器对存储依赖的不确定性。目前还很活跃的编译器都支持了过程间分析和优化,例如gcc -O3就能默认打开优化,open64可以使用-ipa打开,LLVM也有强大的链接时过程间优化,ICC也有。
过程间的分析是基于调用图的。调用图是这样一种图:一个过程由一个节点表示,同时每个调用点也由一个节点表示,若调用点A调用了过程B,就有一条A到B的有向边。简单的面向过程的C或FORTRAN语言,都是直接调用。对于面向对象语言,因为有很多的派生类可能重载同一个函数,因此需要我们事先知道过程的类型,以便决定调用那个具体过程。
除了优化(关于过程间优化的细节可以参考Open64课程–过程间分析优化),其实过程间分析的信息在很多方面都有用。
包括虚函数调用分析,指针别名分析,并行化,软件错误和弱点检测,SQL注入分析,缓冲区溢出检测等。下面分别介绍:
- 虚函数调用分析:面向对象语言有很多的小函数,若每次仅对一个小函数做优化,那收益将很小。对于JAVA这种动态装载类的语言,会使用一个just-in- time编译器在运行时编译bytecode,通常的优化方法是检测函数调用过程,当有较热且短的函数时就将它内联到调用函数中,但是因为动态装载类,所以需要在内联时插入类型检查代码,类型检查代价高,此时若有过程间分析,就能较少这种类型检查,降低开销
- 指针别名分析,对于机器无关优化,往往因为无法知道精确的别名和指针信息而无法作更多的优化,过程间指针和别名分析则能为机器无关优化提供更加精确的别名信息,从而能进行更多优化。
- 并行化,最高效的自动并行化是找出更粗粒度的并行化,如找出最外层循环的并行可能,此时过程间分析就很重要,因为在过程內某个小小的数据依赖都将导致整个循环无法并行化,且将使优化效果大打折扣
- 程序错误和弱点的检测:过程间能分析和检测很多种的程序错误。传统静态分析对于找出很多常见错误模式很有用,现在有些利用程序间分析工具,如PREfix和 Metal,发现更多的错误。虽然这些基于过程间分析的工具找出的错误并不精确,误报率和漏报率都很高,但聊胜于无,毕竟它能为我们的编程差错提供很多的依据。而对于安全方面的弱点,则更加需要能找出所遇可能潜在错误的工具,不管误报多少。在2006年,有两个常用的检测目的,一个是因为缺少对web应用的有效检测而存在的SQL注入,另一个就是C和C++语言中常见的缓冲区溢出。
- SQL注入,对于普通用户而言,SQL语言并没有太多安全隐患,但对于黑客们则大不一样。如下面这段SQL语句,查询一个只有提供用户名和密码才能输出余额的表AcctData(name,password,balance),黑客就能使用语句SELECT balance FROM AcctData WHERE name= ‘Charles Dickens’ ‘ and password= ‘who cares’在避免输入密码的情况下得到balance信息。因为在SQL语句中被用作注释,上面的语句相当于在两个变量里保存了两端查询语句,在过程之间传递,此时若无对真个程序的完全过程间分析,我们没办法检测出SQL注入的错误。
- 缓冲区溢出,如strcpy(b,s),若串s比b的缓冲区要大,则在执行strcpy的过程中将引起s中的字节覆盖了b之外的内存,黑客就能通过精心设计的串s控制计算机程序计数器跳转到任何黑客想要跳转的地方,从而进一步获得管理员权限,入侵对方系统。防止缓冲区溢出,最好的方式是对每个数组写操作都做越界检查,这种越界检查可以是静态手工插入的也可以是动态插入的。对于静态手工插入,人很难做到无一遗漏,也有一些启发式的工具出现,但都无法做到完美,因为用户的实际输入在静态检查时是无法知道的,所有静态的分析就是确保动态检查能适时插入。于是一个很好的方式就是让编译器来完成这项工作。编译器插入将会是非常繁重耗时的工作。而过程间分析能很好的加速动态越界检查。比如,若我们只关心捕捉可能引起缓冲区移除的用户输入,我们就能使用静态分析找出保存用户输入串的变量,然后像SQL注入分析那样,跟踪输入在过程间的拷贝和传播,来消除不必要的越界检查以便降低开销。
参考
http://gcc.gnu.org/projects/tree-profiling.html
《Compilers Principles,Techniques and Tools 》 Second Edition Page 903-921

弱弱问一下,编译器怎么帮助”并行化“呢?程序如果是单进程的。
@donghao, 并行化可以有很多方面,你说的并行应该指的是线程间并行。这里的并行化可以是指令间并行和函数间并行。我对这方面没有调研。但至少,可以通过过程间分析实现些函数间并行。比如某个函数內有一个循环,每次循环都会调用某个函数,且传入的参数要么和循环变量i有关,要么始终不变。此时,编译器就能识别,并将这个函数调用并行化。呵呵。
@donghao, 回答的有些不准确,三级并行应该是:任务级并行(Task Level parallelism),也就是你提到的线程和进程的并行;循环级并行(Loop Level parallelism),就是我上面的评论中的并行;指令级并行(Instruction Level parallelism),比如CPU中的指令调度,流水线,延迟槽等等技术都是为了指令级并行。你可以参考下面三个链接。
http://en.wikipedia.org/wiki/Instruction_level_parallelism
http://en.wikipedia.org/wiki/Task_parallelism
http://en.wikipedia.org/wiki/Data_parallelism
@erlv, 多谢,这个回答很清楚了 :)
@donghao, 不客气:)