mirror of
https://github.com/chenzomi12/aisystem.git
synced 2025-10-20 12:53:45 +08:00
Merge branch 'main' of https://github.com/chenzomi12/AISystem
This commit is contained in:
@ -113,7 +113,7 @@ void demo(double alpha, double *x, double *y)
|
||||
}
|
||||
```
|
||||
|
||||
示例代码中包含 2 FLOPS 操作,分别是乘法(Multiply)和加法(Add),对于每一次计算操作都需要在内存中读取两个数据,$x[i]$ 和 $y[i]$,最后执行一个线性操作,存储到 $y[i]$ 中,其中把加法和乘法融合在一起的操作也可以称作 FMA(Fused Multiply and Add)。
|
||||
示例代码中包含 2 FLOPs 操作,分别是乘法(Multiply)和加法(Add),对于每一次计算操作都需要在内存中读取两个数据,$x[i]$ 和 $y[i]$,最后执行一个线性操作,存储到 $y[i]$ 中,其中把加法和乘法融合在一起的操作也可以称作 FMA(Fused Multiply and Add)。
|
||||
|
||||
在 O(n) 的时间复杂度下,根据 n 的大小迭代计算 n 次,在 CPU 中串行地按指令顺序去执行 $AX+Y$ 程序。以 Intel Xeon 8280 这款芯片为例,其内存带宽是 131 GB/s,内存的延时是 89 ns,这意味着 8280 芯片的峰值算力是在 89 ns 的时间内传输 11659 个字节(byte)数据。$AX+Y$ 将在 89 ns 的时间内传输 16 字节(C/C++中 double 数据类型所占的内存空间是 8 bytes)数据,此时内存的利用率只有 0.14%(16/11659),存储总线有 99.86% 的时间处于空闲状态。
|
||||
|
||||
@ -185,7 +185,7 @@ void fun_axy(int n, double alpha, double *x, double *y)
|
||||
|
||||

|
||||
|
||||
GPU 和 CPU 内存带宽和时延进行比较,在 GPU 中如果把主内存(HBM Memory)作为内存带宽(B/W, bandwidth)的基本单位,L2 缓存的带宽是主内存的 3 倍,L1 缓存的带宽是主存的 13 倍。在真正计算的时候,希望缓存的数据能够尽快的去用完,然后读取下一批数据,此时就会遇到时延(Lentency)的问题。如果将 L1 缓存的延迟作为基本单位,L2 缓存的延迟是 L1 的 5 倍,HBM 的延迟将是 L1 的 15 倍,因此 GPU 需要有单独的显存。
|
||||
GPU 和 CPU 内存带宽和时延进行比较,在 GPU 中如果把主内存(HBM Memory)作为内存带宽(B/W, bandwidth)的基本单位,L2 缓存的带宽是主内存的 3 倍,L1 缓存的带宽是主存的 13 倍。在真正计算的时候,希望缓存的数据能够尽快的去用完,然后读取下一批数据,此时就会遇到时延(Latency)的问题。如果将 L1 缓存的延迟作为基本单位,L2 缓存的延迟是 L1 的 5 倍,HBM 的延迟将是 L1 的 15 倍,因此 GPU 需要有单独的显存。
|
||||
|
||||
假设使用 CPU 将 DRAM(Dynamic Random Access Memory)中的数据传入到 GPU 中进行计算,较高的时延(25 倍)会导致数据传输的速度远小于计算的速度,因此需要 GPU 有自己的高带宽内存 HBM(High Bandwidth Memory),GPU 和 CPU 之间的通信和数据传输主要通过 PCIe 来进行。
|
||||
|
||||
|
@ -136,15 +136,15 @@ IR(Intermediate Representation)中间表示,是编译器中很重要的一
|
||||
|
||||
如图所示,在编译原理中,通常将编译器分为前端和后端。其中,前端会对所输入的程序进行词法分析、语法分析、语义分析,然后生成中间表达形式 IR。后端会对 IR 进行优化,然后生成目标代码。
|
||||
|
||||

|
||||
:width:`550px`
|
||||
<!--  -->
|
||||
<img src="images/01Introduction06.png" alt="IR 示例图" width="550px">
|
||||
|
||||
例如:LLVM 把前端和后端给拆分出来,在中间层明确定义一种抽象的语言,这个语言就叫做 IR。定义了 IR 以后,前端的任务就是负责最终生成 IR,优化器则是负责优化生成的 IR,而后端的任务就是把 IR 给转化成目标平台的语言。LLVM 的 IR 使用 LLVM assembly language 或称为 LLVM language 来实现 LLVM IR 的类型系统,就指的是 LLVM assembly language 中的类型系统。
|
||||
|
||||
因此,编译器的前端,优化器,后端之间,唯一交换的数据结构类型就是 IR,通过 IR 来实现不同模块的解耦。有些 IR 还会为其专门起一个名字,比如:Open64 的 IR 通常叫做 WHIRL IR,方舟编译器的 IR 叫做 MAPLE IR,LLVM 则通常就称为 LLVM IR。
|
||||
|
||||

|
||||
:width:`550px`
|
||||
<!--  -->
|
||||
<img src="images/01Introduction07.png" alt="LLVM 架构图中的 IR" width="550px">
|
||||
|
||||
- **IR 的定义**
|
||||
|
||||
|
@ -188,19 +188,19 @@ GCC 原本使用 C 开发,后来因为 LLVM、 Clang 的崛起,令 GCC 更
|
||||
|
||||
GCC 通常是跨平台软件的编译器首选。有别于一般局限于特定系统与运行环境的编译器,GCC 在所有平台上都使用同一个前端处理程序,产生一样的中介码,因此此中介码在各个其他平台上使用 GCC 编译,有很大的机会可得到正确无误的输出程序。
|
||||
|
||||
- 总结
|
||||
#### 总结
|
||||
|
||||
GNU 计划本来是为了开发一个自由系统来取代 UNIX 的,但是由于开发的内核 hurd 一直不怎么样,这个系统至今都没出稳定版本,然而 GNU 计划中开发的其他一些自由软件,比如 GCC 编译器,却非常的好,在移植到各大操作系统上一直广泛使用至今。
|
||||
GNU 计划本来是为了开发一个自由系统来取代 UNIX 的,但是由于开发的内核 Hurd 一直不怎么样,这个系统至今都没出稳定版本,然而 GNU 计划中开发的其他一些自由软件,比如 GCC 编译器,却非常的好,在移植到各大操作系统上一直广泛使用至今。
|
||||
|
||||
### Clang
|
||||
|
||||
> Clang 是一个 C、 C++、 Objective-C 和 Objective-C++ 编程语言的编译器前端。它采用了底层虚拟机(LLVM)作为其后端。
|
||||
|
||||
Clang 项目在 2005 年由苹果电脑发起,是 LLVM 编译器工具集的前端(front-end),目的是输出代码对应的抽象语法树(Abstract Syntax Tree, AST),并将代码编译成 LLVM Bitcode。接着在后端(back-end)使用 LLVM 编译成平台相关的机器语言。它的目标是提供一个 GNU 编译器套装(GCC)的替代品。Clang 项目包括 Clang 前端和 Clang 静态分析器等。
|
||||
Clang 项目在 2005 年由苹果电脑发起,是 LLVM 编译器工具集的前端(front-end),目的是输出代码对应的抽象语法树(Abstract Syntax Tree, AST),并将代码编译成 LLVM Bitcode。接着在后端(back-end)使用 LLVM 编译成平台相关的机器语言。它的目标是提供一个 GNU 编译器套装(GCC)的替代品。Clang 课程包括 Clang 前端和 Clang 静态分析器等。
|
||||
|
||||
Clang 本身性能优异,其生成的 AST 所耗用掉的内存仅仅是 GCC 的 20% 左右。FreeBSD 10 将 Clang/LLVM 作为默认编译器。测试证明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍,还能针对用户发生的编译错误准确地给出建议。
|
||||
|
||||
- Clang 历史
|
||||
#### Clang 历史
|
||||
|
||||
Apple 吸收 Chris Lattner 的目的要比改进 GCC 代码优化宏大得多,GCC 系统庞大而笨重,而 Apple 在 MAC 系统大量使用的 Objective-C 在 GCC 的项目支持优先级中比较低。此外 GCC 作为一个纯粹的编译系统,与 IDE 配合得很差。
|
||||
|
||||
@ -210,7 +210,7 @@ Apple 吸收 Chris Lattner 的目的要比改进 GCC 代码优化宏大得多,
|
||||
|
||||
正像名字所写的那样,Clang 只支持 C,C++ 和 Objective-C 三种 C 家族语言。2007 年开始开发,C 编译器最早完成,而由于 Objective-C 相对简单,只是 C 语言的一个简单扩展,很多情况下甚至可以等价地改写为 C 语言对 Objective-C 运行库的函数调用,因此在 2009 年时,已经完全可以用于生产环境。C++ 的支持也热火朝天地进行着。
|
||||
|
||||
- 总结
|
||||
#### 总结
|
||||
|
||||
GCC 目前作为跨平台编译器来说它的兼容性无异是最强的,兼容最强肯定是以牺牲一定的性能为基础,苹果为了提高性能,因此专门针对 mac 系统开发了专用的编译器 Clang 与 LLVM,Clang 用于编译器前段,LLVM 用于后端。
|
||||
|
||||
@ -220,7 +220,7 @@ GCC 目前作为跨平台编译器来说它的兼容性无异是最强的,兼
|
||||
|
||||
LLVM 作为一个编译器的基础建设,它是为了任意一种编程语言写成的程序,利用虚拟技术,创造出编译时期,链结时期,运行时期以及“闲置时期”的优化。
|
||||
|
||||
- LLVM 历史
|
||||
#### LLVM 历史
|
||||
|
||||
Apple 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编译器标准一直做得不错,但 Apple 对编译工具会提出更高的要求:
|
||||
|
||||
@ -232,7 +232,7 @@ Apple 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编
|
||||
|
||||

|
||||
|
||||
- 总结
|
||||
#### 总结
|
||||
|
||||
因为 GCC 的编译器已经慢慢无法满足苹果的需求,因此苹果开发了 Clang 与 LLVM 来完全取代 GCC。Xcode4 之后,苹果的默认编译器采用 Clang 作为编译器前端,LLVM 作为编译器后端。
|
||||
|
||||
@ -240,7 +240,7 @@ Apple 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编
|
||||
|
||||
下面来通过不同的维度来比较一下两大编译器巨头 GCC 和 Clang:
|
||||
|
||||
- **开源软件**:众所周知,GCC 和 Clang 都是免费的开源软件。但是他们的许可授权很不一样。GCC 是参照 GPL(GNU 公共许可证)授权的,而 Clang/LLVM 是 Apache 许可授权的。比较 GCC 和 Clang 的许可授权,最专业的是律师。
|
||||
- **开源软件**:众所周知,GCC 和 Clang 都是免费的开源软件。但是他们的许可授权很不一样。GCC 是参照 GPL(GNU 公共许可证)授权的,而 Clang/LLVM 是 Apache 许可授权的。要深入比较这两种许可证在法律层面的细微差别及其对商业应用和代码衍生的具体约束,最专业的判断往往来自于精通软件许可的律师,而非单纯的技术人员。
|
||||
|
||||
- **支持平台**:GCC 和 Clang 都支持几乎所有的平台。Clang/LLVM 可在 Windows 本机上进行编译,而 GCC 则需要 MinGW 这样的子系统,才能与 Windows 兼容。这样比较 Clang 和 GCC 是不公平的,因为 GCC 没有在本地支持 Windows 的计划。
|
||||
|
||||
@ -249,7 +249,8 @@ Apple 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编
|
||||
- **标准支持**:对 C++ 20,即最新推出的 C++ 版本,GCC 已通过测试。另外,它也完全符合 C++ 17 以及最新的 C 语言标准,C17。Clang 完全符合 C++ 17 标准,也将很快跟进 C++ 20 标准。
|
||||
|
||||
- **高效代码生成**:Clang 和 GCC 的代码生成,在空间和时间的复杂度旗鼓相当。因此这种比较毫无意义,因为这两个编译优化工具都基于一种严密的静态分配形式。
|
||||
语言独立的类型系统——在这个标题下对比 Clang 与 GCC 很有意义。由于 Clang/LLVM 对所有兼容语言都使用语言独立的类型系统,因此可以确定指令的确切语义。GCC 则没有语言独立类型系统的设计目标。
|
||||
- **语言独立的类型系统**
|
||||
在这个标题下对比 Clang 与 GCC 很有意义。由于 Clang/LLVM 对所有兼容语言都使用语言独立的类型系统,因此可以确定指令的确切语义。GCC 则没有语言独立类型系统的设计目标。
|
||||
|
||||
- **前端解析器**:GCC 以前有基于 Bison 的 LR 解析器,后来转向了手写递归下降解析器。Clang 一直使用手写的确定性递归下降解析器,且可回溯。
|
||||
|
||||
@ -269,7 +270,7 @@ Clang 是基于 LLVM 的主要支持 C、C++、Objective-C 和 Objective-C++ 编
|
||||
|:--:|:--:|:--:|
|
||||
| 许可证 | GNU GPL | Apache 2.0 |
|
||||
| 代码模块化 | 一体化架构 | 模块化
|
||||
| 支持平台 | Uinx, Windows、MAC | Uinx、MAC |
|
||||
| 支持平台 | Uinx、Windows、MAC | Uinx、Windows、MAC |
|
||||
| 代码生成 | 高效,有很多编译器选项可以使用 | 高效,LLVM 后端使用了 SSA 表单 |
|
||||
| 语言独立类型系统 | 没有 | 有 |
|
||||
| 构建工具 | Make Base | CMake |
|
||||
|
@ -57,29 +57,29 @@ gcc -E hello.c -o hello.i
|
||||
|
||||
- 头文件展开:
|
||||
|
||||
在预处理阶段,编译器会将源文件中包含的头文件内容插入到源文件中对应的位置,以便在编译时能够访问头文件中定义的函数、变量、宏等内容。
|
||||
在预处理阶段,编译器会将源文件中包含的头文件内容插入到源文件中对应的位置,以便在编译时能够访问头文件中定义的函数、变量、宏等内容。
|
||||
|
||||
- 宏替换:
|
||||
|
||||
在预处理阶段,编译器会将源文件中定义的宏在使用时进行替换,即将宏名称替换为其定义的内容。这样可以简化代码编写,提高代码的可读性和可维护性。
|
||||
在预处理阶段,编译器会将源文件中定义的宏在使用时进行替换,即将宏名称替换为其定义的内容。这样可以简化代码编写,提高代码的可读性和可维护性。
|
||||
|
||||
- 条件编译:
|
||||
|
||||
通过预处理指令如 #if、#else、#ifdef 等,在编译前确定某些代码片段是否应被包含在最终的编译过程中。这样可以根据条件编译选择性地包含代码,实现不同平台、环境下的代码控制。
|
||||
通过预处理指令如 `#if`、`#else`、`#ifdef` 等,在编译前确定某些代码片段是否应被包含在最终的编译过程中。这样可以根据条件编译选择性地包含代码,实现不同平台、环境下的代码控制。
|
||||
|
||||
- 删除注释:
|
||||
|
||||
在预处理阶段,编译器会删除源文件中的注释,包括单行注释(//)和多行注释(/.../),这样可以提高编译速度并减少编译后代码的大小。
|
||||
在预处理阶段,编译器会删除源文件中的注释,包括单行注释(//)和多行注释(/.../),这样可以提高编译速度并减少编译后代码的大小。
|
||||
|
||||
- 添加行号和文件名标识:
|
||||
|
||||
通过预处理指令如 #line,在预处理阶段添加行号和文件名标识到源文件中,便于在编译过程中定位错误信息和调试。
|
||||
通过预处理指令如 `#line`,在预处理阶段添加行号和文件名标识到源文件中,便于在编译过程中定位错误信息和调试。
|
||||
|
||||
- 保留 #pragma 命令:
|
||||
- 保留 `#pragma` 命令:
|
||||
|
||||
在预处理阶段,编译器会保留以#pragma 开头的预处理指令,如#pragma once、#pragma pack 等,这些指令可以用来指导编译器进行特定的处理,如控制编译器的行为或优化代码。
|
||||
在预处理阶段,编译器会保留以 `#pragma` 开头的预处理指令,如 `#pragma once`、`#pragma pack` 等,这些指令可以用来指导编译器进行特定的处理,如控制编译器的行为或优化代码。
|
||||
|
||||
hello.i 文件部分内容如下,详细可见../code/gcc/hello.i 文件。
|
||||
`hello.i` 文件部分内容如下,详细可见 `../code/gcc/hello.i` 文件。
|
||||
|
||||
```c
|
||||
int main(void){
|
||||
@ -88,23 +88,23 @@ int main(void){
|
||||
}
|
||||
```
|
||||
|
||||
在该文件中,已经将头文件包含进来,宏定义 HELLOWORD 替换为字符串"hello world\n",并删除了注释和多余空白字符。
|
||||
在该文件中,已经将头文件包含进来,宏定义 `HELLOWORD` 替换为字符串 `"hello world\n"`,并删除了注释和多余空白字符。
|
||||
|
||||
### 编译(ccl)
|
||||
|
||||
在这里,编译并不仅仅指将程序从源文件转换为二进制文件的整个过程,而是特指将经过预处理的文件(hello.i)转换为特定汇编代码文件(hello.s)的过程。
|
||||
在这里,编译并不仅仅指将程序从源文件转换为二进制文件的整个过程,而是特指将经过预处理的文件(`hello.i`)转换为特定汇编代码文件(`hello.s`)的过程。
|
||||
|
||||
在这个过程中,经过预处理后的 .i 文件作为输入,通过编译器(ccl)生成相应的汇编代码.s 文件。编译器(ccl)是 GCC 的前端,其主要功能是将经过预处理的代码转换为汇编代码。编译阶段会对预处理后的.i 文件进行语法分析、词法分析以及各种优化,最终生成对应的汇编代码。
|
||||
在这个过程中,经过预处理后的 `.i` 文件作为输入,通过编译器(ccl)生成相应的汇编代码 `.s` 文件。编译器(ccl)是 GCC 的前端,其主要功能是将经过预处理的代码转换为汇编代码。编译阶段会对预处理后的 `.i` 文件进行语法分析、词法分析以及各种优化,最终生成对应的汇编代码。
|
||||
|
||||
汇编代码是以文本形式存在的程序代码,接着经过编译生成.s 文件,是连接程序员编写的高级语言代码与计算机硬件之间的桥梁。
|
||||
汇编代码是以文本形式存在的程序代码,接着经过编译生成 `.s` 文件,是连接程序员编写的高级语言代码与计算机硬件之间的桥梁。
|
||||
|
||||
生成文件 hello.s:
|
||||
生成文件 `hello.s`:
|
||||
|
||||
```shell
|
||||
gcc -S hello.i -o hello.s
|
||||
```
|
||||
|
||||
打开 hello.s 后输出如下:
|
||||
打开 `hello.s` 后输出如下:
|
||||
|
||||
```
|
||||
.section __TEXT,__text,regular,pure_instructions
|
||||
@ -139,15 +139,15 @@ L_.str: ## @.str
|
||||
.subsections_via_symbols
|
||||
```
|
||||
|
||||
现在 hello.s 文件中包含了完全是汇编指令的内容,表明 hello.c 文件已经被成功编译成了汇编语言。
|
||||
现在 `hello.s` 文件中包含了完全是汇编指令的内容,表明 `hello.c` 文件已经被成功编译成了汇编语言。
|
||||
|
||||
### 汇编(as)
|
||||
|
||||
在这一步中,我们将汇编代码转换成机器指令。这一步是通过汇编器(as)完成的。汇编器是 GCC 的后端,其主要功能是将汇编代码转换成机器指令。
|
||||
|
||||
汇编器的工作是将人类可读的汇编代码转换为机器指令或二进制码,生成一个可重定位的目标程序,通常以 .o 作为文件扩展名。这个目标文件包含了逐行转换后的机器码,以二进制形式存储。这种可重定位的目标程序为后续的链接和执行提供了基础,使得汇编代码能够被计算机直接执行。
|
||||
汇编器的工作是将人类可读的汇编代码转换为机器指令或二进制码,生成一个可重定位的目标程序,通常以 `.o` 作为文件扩展名。这个目标文件包含了逐行转换后的机器码,以二进制形式存储。这种可重定位的目标程序为后续的链接和执行提供了基础,使得汇编代码能够被计算机直接执行。
|
||||
|
||||
生成文件 hello.o
|
||||
生成文件 `hello.o`
|
||||
|
||||
```shell
|
||||
gcc -c hello.s -o hello.o
|
||||
@ -161,7 +161,7 @@ gcc -c hello.s -o hello.o
|
||||
gcc -o hello.o -o hello
|
||||
```
|
||||
|
||||
添加-v 参数,可以查看详细的编译过程:
|
||||
添加 `-v` 参数,可以查看详细的编译过程:
|
||||
|
||||
```shell
|
||||
gcc -v hello.c -o hello
|
||||
|
@ -169,7 +169,7 @@ attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0"
|
||||
4. 然后加载 %3 和 %4 的值,进行加法操作,并将结果存储至 %5 中
|
||||
5. 最后,程序返回整数值 0
|
||||
|
||||
LLVM IR 的代码和 C 语言编译生成的代码在功能实现上具有完全相同的特性。.ll 文件作为 LLVM IR 的一种中间语言,可以通过 LLVM 编译器将其转换为机器码,从而实现计算机程序的执行。
|
||||
LLVM IR 的代码和 C 语言编译生成的代码在功能实现上具有完全相同的特性。`.ll` 文件作为 LLVM IR 的一种中间语言,可以通过 LLVM 编译器将其转换为机器码,从而实现计算机程序的执行。
|
||||
|
||||
### 基本语法
|
||||
|
||||
@ -192,7 +192,7 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
在经过编译后的 .ll 文件的内容如下所示:
|
||||
在经过编译后的 `.ll` 文件的内容如下所示:
|
||||
|
||||
```llvm
|
||||
define i32 @main() #0 {
|
||||
@ -220,9 +220,9 @@ return: ; preds = %if.else, %if.then
|
||||
}
|
||||
```
|
||||
|
||||
`icmp` 指令是根据比较规则,比较两个操作数,将比较的结果以布尔值或者布尔值向量返回,且对于操作数的限定是操作数为整数或整数值向量、指针或指针向量。其中,eq 是比较规则,%rem 和 0 是操作数,i32 是操作数类型,比较 %rem 与 0 的值是否相等,将比较的结果存放到 %cmp 中。
|
||||
`icmp` 指令是根据比较规则,比较两个操作数,将比较的结果以布尔值或者布尔值向量返回,且对于操作数的限定是操作数为整数或整数值向量、指针或指针向量。其中,`eq` 是比较规则,`%rem` 和 `0` 是操作数,`i32` 是操作数类型,比较 `%rem` 与 `0` 的值是否相等,将比较的结果存放到 `%cmp` 中。
|
||||
|
||||
`br` 指令有两种形式,分别对应于条件分支和无条件分支。该指令的条件分支在形式上接受一个“i1”值和两个“label”值,用于将控制流传输到当前函数中的不同基本块,上面这条指令是条件分支,类似于 c 中的三目条件运算符 `< expression ?Statement:statement>`;无条件分支的话就是不用判断,直接跳转到指定的分支,类似于 c 中 goto ,比如说这个就是无条件分支 br label %return。`br i1 %cmp, label %if.then, label %if.else` 指令的意思是,i1 类型的变量 %cmp 的值如果为真,执行 `if.then` 否则执行 `if.else`。
|
||||
`br` 指令有两种形式,分别对应于条件分支和无条件分支。该指令的条件分支在形式上接受一个“i1”值和两个“label”值,用于将控制流传输到当前函数中的不同基本块,上面这条指令是条件分支,类似于 c 中的三目条件运算符 `< expression ?Statement:statement>`;无条件分支的话就是不用判断,直接跳转到指定的分支,类似于 c 中 goto ,比如说这个就是无条件分支 `br label %return`。`br i1 %cmp, label %if.then, label %if.else` 指令的意思是,i1 类型的变量 %cmp 的值如果为真,执行 `if.then` 否则执行 `if.else`。
|
||||
|
||||
2. 循环体
|
||||
|
||||
@ -243,7 +243,7 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
在经过编译后的 .ll 文件的内容如下所示:
|
||||
在经过编译后的 `.ll` 文件的内容如下所示:
|
||||
|
||||
```llvm
|
||||
define i32 @main() #0 {
|
||||
@ -294,7 +294,7 @@ int main(){
|
||||
}
|
||||
```
|
||||
|
||||
在经过编译后的 .ll 文件的内容如下所示:
|
||||
在经过编译后的 `.ll` 文件的内容如下所示:
|
||||
|
||||
```llvm
|
||||
@.str = private unnamed_addr constant [16 x i8] c"i\E7\9A\84\E5\80\BC\E4\B8\BA\EF\BC\9A%d\00", align 1
|
||||
@ -323,7 +323,7 @@ entry:
|
||||
declare i32 @printf(i8*, ...)
|
||||
```
|
||||
|
||||
对指针的操作就是指针的指针,开辟一块指针类型的内存,里面放个指针`%pi = alloca i32*, align 8
|
||||
对指针的操作就是指针的指针,开辟一块指针类型的内存,里面放个指针 `%pi = alloca i32*, align 8`。
|
||||
|
||||
## 小结与思考
|
||||
|
||||
|
@ -41,13 +41,13 @@ LLVM IR 的设计理念类似于精简指令集(RISC),这意味着它倾
|
||||
|
||||
| 指令类型 | 指令格式 | 四元组表示 |
|
||||
| -- | - | --- |
|
||||
| 赋值指令 | z = x| (`=`, `x`, ``, `z`)|
|
||||
| 赋值指令 | z = x| (`=`, `x`, ` `, `z`)|
|
||||
| 算术指令 | z = x op y | (`op`, `x`, `y`, `z`) |
|
||||
| 一元运算 | z = op y| (`op`, `y`, ``, `z`) |
|
||||
| 条件跳转 | if x goto L| (`if`, `x`, ``, `L`) |
|
||||
| 无条件跳转 | goto L | (`goto`, ``, ``, `L`) |
|
||||
| 一元运算 | z = op y| (`op`, `y`, ` `, `z`) |
|
||||
| 条件跳转 | if x goto L| (`if`, `x`, ` `, `L`) |
|
||||
| 无条件跳转 | goto L | (`goto`, ` `, ` `, `L`) |
|
||||
| 函数调用 | z = call f(a, b) | (`call`, `f`, `(a,b)`, `z`) |
|
||||
| 返回指令 | return x| (`return`, `x`, ``, ``) |
|
||||
| 返回指令 | return x| (`return`, `x`, ` `, ` `) |
|
||||
|
||||
3. 三地址码的优点
|
||||
|
||||
@ -101,35 +101,27 @@ entry:
|
||||
LLVM IR 是一种通用的、低级的虚拟指令集,用于编译器和工具链开发。以下是关于 LLVM IR 的指导原则和最佳实践的总结:
|
||||
|
||||
1. 模块化设计
|
||||
|
||||
LLVM IR 设计为模块化的,代码和数据分为多个模块,每个模块包含多个函数、全局变量和其他定义。这种设计支持灵活的代码生成和优化。
|
||||
|
||||
2. 中间表示层次
|
||||
|
||||
LLVM IR 是编译过程中的中间表示,位于源代码和机器码之间。这种层次化设计使得不同语言和目标架构可以共享通用的优化和代码生成技术。
|
||||
|
||||
3. 静态单赋值形式(SSA)
|
||||
|
||||
LLVM IR 采用 SSA 形式,每个变量在代码中只被赋值一次。SSA 形式简化了数据流分析和优化,例如死代码消除和寄存器分配。
|
||||
|
||||
4. 类型系统
|
||||
|
||||
LLVM IR 使用强类型系统,支持基本类型(如整数、浮点数)和复合类型(如数组、结构体)。类型系统确保了操作的合法性并支持类型检查和转换。
|
||||
|
||||
5. 指令集
|
||||
|
||||
LLVM IR 提供丰富的指令集,包括算术运算、逻辑运算、内存操作和控制流指令。每条指令都指定了操作数类型,确保了代码的可移植性和一致性。
|
||||
|
||||
6. 优化和扩展
|
||||
|
||||
LLVM IR 支持多种优化技术,包括常量折叠、循环优化和内联展开。它还支持通过插件和扩展添加自定义优化和分析。
|
||||
|
||||
7. 目标无关性
|
||||
|
||||
LLVM IR 设计为目标无关的中间表示,可以跨不同的硬件和操作系统使用。这种目标无关性简化了跨平台编译和优化。
|
||||
|
||||
8. 调试支持
|
||||
|
||||
LLVM IR 包含丰富的调试信息支持,可以生成调试符号和源代码映射,支持调试器如 GDB 和 LLDB。
|
||||
|
||||
这些原则和最佳实践使 LLVM IR 成为一个强大且灵活的工具,用于编译器开发和代码优化。它的模块化设计、强类型系统、丰富的指令集和目标无关性使其适用于广泛的应用场景,从语言前端到高级优化和代码生成。
|
||||
|
@ -178,7 +178,7 @@ AST 节点 CompoundStmt 包含 `if` 和 `return` 语句,`IfStmt` 和 `ReturnSt
|
||||
|
||||
### 语义分析
|
||||
|
||||
语法分析(Semantic Analyze)主要关注代码结构是否符合语法规则,而语义分析则负责确保代码的含义和逻辑正确。在语义分析阶段,编译器会检查变量的类型是否匹配、函数调用是否正确、表达式是否合理等,以确保代码在运行时不会出现逻辑错误。
|
||||
语法分析(Syntactic Analyze)主要关注代码结构是否符合语法规则,而语义分析(Semantic Analyze)则负责确保代码的含义和逻辑正确。在语义分析阶段,编译器会检查变量的类型是否匹配、函数调用是否正确、表达式是否合理等,以确保代码在运行时不会出现逻辑错误。
|
||||
|
||||
语义分析借助符号表来检验代码是否符合语言类型系统。符号表存储标识符和其对应的类型之间的映射,以及其他必要信息。一种直观的类型检查方法是在解析阶段之后,遍历抽象语法树(AST),同时从符号表中获取关于类型的信息。
|
||||
|
||||
@ -237,7 +237,7 @@ LLVM 优化层在输入的时候是一个 AST 语法树,输出的时候已经
|
||||
|
||||
优化过程需要执行以下代码:
|
||||
|
||||
首先我们需要生成 hello.bc 文件:
|
||||
首先我们需要生成 `hello.bc` 文件:
|
||||
|
||||
```shell
|
||||
clang -emit-llvm -c hello.c -o hello.bc
|
||||
@ -247,7 +247,7 @@ clang -emit-llvm -c hello.c -o hello.bc
|
||||
```shell
|
||||
opt -passes='instcount,adce,mdgc' -o hello-tmp.bc hello.bc -stats
|
||||
```
|
||||
就可以生成 hello-tmp.bc 文件,其中包含了优化后的 IR。
|
||||
就可以生成 `hello-tmp.bc` 文件,其中包含了优化后的 IR。
|
||||
|
||||
在上述过程中有很多不同阶段的 pass。
|
||||
|
||||
|
@ -118,9 +118,9 @@ Code Emission(代码生成)是 LLVM 后端的重要阶段,其目标是将
|
||||
|
||||
在 LLVM 中,Code Emission 由以下组件共同完成:
|
||||
|
||||
1. **指令选择器(Instruction Selector)** 指令选择器负责从 LLVM IR 中选择合适的目标机器指令。LLVM 使用多种指令选择算法,包括基于树模式匹配的`SelectionDAG`和基于表格驱动的`GlobalISel`。指令选择器将中间表示转化为机器指令的中间表示。
|
||||
1. **指令选择器(Instruction Selector)** 指令选择器负责从 LLVM IR 中选择合适的目标机器指令。LLVM 使用多种指令选择算法,包括基于树模式匹配的 `SelectionDAG` 和基于表格驱动的 `GlobalISel`。指令选择器将中间表示转化为机器指令的中间表示。
|
||||
|
||||
2. **指令调度器(Instruction Scheduler)** 指令调度器优化指令的执行顺序,以减少依赖关系和提高指令级并行性。LLVM 的调度器包括`SelectionDAG`调度器和机器码层的调度器,后者在目标机器码生成前优化指令序列。
|
||||
2. **指令调度器(Instruction Scheduler)** 指令调度器优化指令的执行顺序,以减少依赖关系和提高指令级并行性。LLVM 的调度器包括 `SelectionDAG` 调度器和机器码层的调度器,后者在目标机器码生成前优化指令序列。
|
||||
|
||||
3. **寄存器分配器(Register Allocator)** 寄存器分配器负责将虚拟寄存器映射到物理寄存器。LLVM 提供了多种寄存器分配算法,包括线性扫描分配器和基于图着色的分配器。寄存器分配器的目标是最小化寄存器溢出和寄存器间的冲突。
|
||||
|
||||
@ -184,17 +184,17 @@ Youtube 上 LLVM 之父 Chris Lattner:编译器的黄金时代
|
||||
|
||||

|
||||
|
||||
2. XLA:优化机器学习编译器
|
||||
3. XLA:优化机器学习编译器
|
||||
|
||||
XLA(加速线性代数)是谷歌推出的一种针对特定领域的线性代数编译器,能够加快 TensorFlow 模型的运行速度,而且可能完全不需要更改源代码。
|
||||
|
||||
TensorFlow 中大部分代码和算子都是通过 XLA 编译的,XLA 的底层就是 LLVM,所以 XLA 可以利用到 LLVM 的很多特性,比如优化、代码生成、并行计算等。
|
||||
|
||||
3. JAX:高性能的数值计算库
|
||||
4. JAX:高性能的数值计算库
|
||||
|
||||
JAX 是 Autograd 和 XLA 的结合,JAX 本身不是一个深度学习的框架,他是一个高性能的数值计算库,更是结合了可组合的函数转换库,用于高性能机器学习研究。
|
||||
|
||||
4. TensorFlow:机器学习平台
|
||||
5. TensorFlow:机器学习平台
|
||||
|
||||
TensorFlow 是一个端到端开源机器学习平台。它拥有一个全面而灵活的生态系统,其中包含各种工具、库和社区资源,可助力研究人员推动先进机器学习技术。
|
||||
|
||||
@ -202,7 +202,7 @@ TensorFlow 可以更好的应用于工业生产环境,因为它可以利用到
|
||||
|
||||

|
||||
|
||||
5. TVM 到端深度学习编译器
|
||||
6. TVM 到端深度学习编译器
|
||||
|
||||
为了使得各种硬件后端的计算图层级和算子层级优化成为可能,TVM 从现有框架中取得 DL 程序的高层级表示,并产生多硬件平台后端上低层级的优化代码,其目标是展示与人工调优的竞争力。
|
||||
|
||||
|
@ -31,6 +31,4 @@
|
||||
>
|
||||
> 欢迎大家使用的过程中发现 bug 或者勘误直接提交代码 PR 到开源社区哦!
|
||||
>
|
||||
> 欢迎大家使用的过程中发现 bug 或者勘误直接提交 PR 到开源社区哦!
|
||||
>
|
||||
> 请大家尊重开源和 ZOMI 的努力,引用 PPT 的内容请规范转载标明出处哦!
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
- [**《后端优化》**](./04Backend/)后端优化作为 AI 编译器跟硬件之间的相连接的模块,更多的是算子或者 Kernel 进行优化,而优化之前需要把计算图转换称为调度树等 IR 格式,AI 编译器为了更好地跟硬件打交道,充分赋能硬件,需要后端优化来支持,于是后端针对调度树或者底层 IR,在每一个算子/Kernel 进行循环优化、指令优化和内存优化等技术。
|
||||
|
||||
- **《多面体技术》**多面体不属于新的技术,反而是传统编译器的一种优化手段,得益于深度学习中的主要特征(循环、张量),因此多面体技术可以发挥更大的作用,对循环展开、内存映射等优化工作。多面体表示技术作为统一化的程序变换表示技术, 可以通过迭代域、仿射调度、访存函数等操作对算子或者 Kernel 进行循环优化和内存映射优化,作为 AI 编译器的前言研究方向。
|
||||
- [**《多面体技术》**]()多面体不属于新的技术,反而是传统编译器的一种优化手段,得益于深度学习中的主要特征(循环、张量),因此多面体技术可以发挥更大的作用,对循环展开、内存映射等优化工作。多面体表示技术作为统一化的程序变换表示技术, 可以通过迭代域、仿射调度、访存函数等操作对算子或者 Kernel 进行循环优化和内存映射优化,作为 AI 编译器的前言研究方向。
|
||||
|
||||
- [**《PyTorch 图模式》**](./06PyTorch/)在充分了解 AI 编译器后,来深度剖析 PyTorch2.0 关于图模式的 Dynamo 是如何实现的,如何对 PyTorch 的后端执行进行加速。本节会以实际的 AI 框架 PyTorch 2.0 为主线,去把其主打特性 Dynamo 和 AOTAutograd 进行展开,并回顾 PyTorch 对图模式的尝试,了解现今最热门的 AI 框架如何进行编译器优化的。
|
||||
|
||||
|
Reference in New Issue
Block a user