LLVM 的DSA与APA优化

发布时间 2023-05-26 04:14:20作者: 吴建明wujianming

LLVM在链接时所做的最激进的优化,莫过于DSA和APA。在DSA分析中,借助于LLVM比较充足的type information,在指针分析的基础上,可以构造出整个内存对象的连接关系图。然后对这个图进行分析,得到内存对象的连接模式,将连接比较紧密的结构对象,例如树、链表等结构体分配在自定义的一格连续分配的内存池中。这样可以少维护很多内存块,并且大大提高空间locality,相应的提高cache命中率。

APA(Automatic Pool Allocation)能够将堆上分配的链接形式的结构体,分配在连续的内存池中,这种做法是通过将内存分配函数替换为自定义池

分配函数实现的,示意图如下所示:

 另外一些在链接阶段进行的分析,包括调用图构建,Mod/Ref分析,以及一些过程间的分析,例如函数inline,死全局变量删除(dead global elimination),死实参删除(dead argument),常量传播,列表边界检查消除(array bounds check elimination),简单结构体域重排(structure field reordering),以及Automatic Pool Allocation。

在编译时会汇总每个函数摘要信息(prodedure summary),附在LLVM IR中,在链接时就无需重新从源码中获取信息,直接使用函数摘要进行过程间分析即可。这种技术大大缩短了增量编译的时间。函数摘要一直是过程间分析的重点,因为这种技术在不过分影响精确性的前提下,大大提高了静态分析的效率。

这里简单提示一下,结构体域重排(structure field reordering)会涉及到以下问题:

1)为什么需要结构体域重排。

2)结构体域重排应该怎么做。

3)结构体域重排会不会带来其它影响。

另外结构体域重排,也可以与hot概念相结合,如下代码所示:

// s has hot member and cold member

struct s{

        char c;

        int i;

        double d;

};

// we can split s to two struct

struct hots{

    char c;

    double d;

};

struct colds{

    int i;

};

将结构体根据hot的程度分拆成两个结构体,可以针对两个结构体进行不同的优化,例如,将hot结构体的member提升到register中等。

与结构体域重排优化概念相关的另外概念是padding,alignment,point compression。前两个概念与结构体域重排直接相关,示意代码如下所示:

struct s1 {

        char a; // here padding 3 bytes

        int b;

        char c;

        char d;

        xhar e; // here padding 1 bytes

}

这个结构体的sizeof是12bytes,但是,如果将这个s1结构体子域重排的话,8bytes就够了,如下图所示:

struct s1 {

        char a;

        char c;

        char d;

        char e;

        int b;

}

首先就是,要做reordering的前提就是,必须能够确保当前结构体只在当前TU中,如果当前结构体在另外的TU中也存在的话,那么就会存在结构体内存布局不相容的情况。所以这样的优化只能在链接时进行。

另外,就是要相应修改所有对struct进行引用的操作,在C/C++这些类型不安全的语言中,类型转换非常普遍,有可能另外一种类型的使用其实就是在当前类型那个内存上进行的,如下代码所示。如何识别跟踪这些类型转换是一格非常困难的问题,LLVM IR提供了type information来帮助执行这些优化。

struct s {

        char a;

    int b;

    char c;

};

 

struct s_head {

    char a;

};

struct s_ext {

    char a;

    int b;

    char c;

    int d;

    char e;

};

 

struct S s;

struct S_head *head = (struct S_head*)&s;

fn1(head);

 

struct S_ext ext;

struct S *sp = (struct S*)&ext;

fn2(sp);

另一个与reordering the fields相关的概念就是point compression,这两者的目的都是用于提高内存使用率,使内存显得更为紧凑。与reordering hte fields类似的两个优化概念是Structure peeling,structure splitting,这些都属于Data Layout Optimizations。

LLVM代码生成器

在寄存器分配之前,LLVM会一直保存SSA形式。LLVM代码生成结构如下图所示:

 图中描述了在代码生成时用到的一些技术。

装载时优化

运行时优化

在大部分人看来,运行时优化仅仅与JIT、虚拟机和CPU乱序发射以及cache相关,因为运行时优化只在运行时执行,JIT可以结合虚拟机在程序解释执行时识别出hot区域,以便能够将这些代码编译成native code,CPU乱序可以依据当前指令的相关性来执行指令重排等操作,而cache也可以将频繁出现的内容缓存到cache中以便加快执行速度。可是,LLVM与这些貌似都无关,确实LLVM运行时优化与他们不相同。LLVM运行时优化与闲时优化相互结合来实现相关优化,LLVM会在代码生成的时候插入一些低代价的指令来收集运行时的信息(profiling information),这些信息用于指导闲时优化重新生成一份更加高效的native code。这个过程也可以重复多次来达到较优的效果。一个重要的用处就是识别hot loop region和hot path,然后再对这些热区域进行特殊处理。