草庐IT

程序分析与优化 - 1 导论

周荣华 2023-03-28 原文

本章是整个课程的第一章,主要介绍了一下文章的起源,编译器的历史和相关概念。

本文中的所有内容来自学习DCC888的学习笔记或者自己理解的整理,如需转载请注明出处。周荣华@燧原科技

 

1. 导论

1.1. 什么是DCC888

DCC是葡萄牙语Departamento de Ciência da Computação的简称,翻译成中文就是计算机科学学院。DCC是巴西UFMG(Federal University of Minas Gerais,中文米纳斯吉拉斯州联邦大学)下面的一个学院。DCC888是UFMG里面计算机学院的Fernado教授开通的一个课程,原名是CODE ANALYSIS AND OPTIMIZATION,也可以称为PROGRAM ANALYSIS AND OPTIMIZATION,翻译过来就是代码或者程序的分析和优化,课程结合离散数据、图论等理论知识讲解了代码的分析和优化的过程和方法,并以LLVM为例子讲解了相关实践过程。

1.2. 为什么要学习DCC888

各个学校针对编译原理相关的课程很多,但编译优化的可能比较少,结合当前主流的LLVM编译器的理论联系实际的可能更有限。DCC888不论从理论,还是从实践上来说,都是难得的好教程。

另外,计算机科学从上世纪50年代兴起到今天,历经了早期的专用计算机,到上世纪90年的PC和通用计算机的普及,当前计算机科学面临从通用计算到各种异构计算并存的时代转变。为了能在不同芯片上都发挥出代码的最优性能,一方面,每个芯片设计公司需要专注对自己芯片做专有的优化;另外一方面,提供云基础设施的集成厂商,也需要做一些通用的编译优化,已实现跨芯片运算优化;对每个程序设计师而言,了解下层的编译优化过程,对自己代码实现的性能最优,也会有比较大的帮助。

套用LLVM之父Chris Lattner的一句话就是,(现在是)计算机体系架构的黄金时代,也是编译器的黄金时代。

1.3. 编译器缔造者

计算机科学发展到现在,程序员面对的编程语言从最底层的机器码,到各种汇编语言,基础的C语言,到各种高级语言,到现在的无代码平台,为了提升程序员的生产力,编程语言不断抽象,越来越接近现实人类世界。

相对的,物理层面,芯片本身也在不断更新换代,新的硬件技术层出不穷,怎么让很久之前写的老代码在新的芯片架构上运行,并且发挥出更高的性能,是每个芯片设计商需要特别关注的。

编译器就是人类世界和机器世界的桥梁,没有编译器编程语言做的任何优化都无法落地,更加谈不上更高性能。

 

1.4. 课程目标

1.4.1. 了解编译过程

  • 编译器如何自动的将程序转换成计算机可识别的代码(学习编译器的前端、中端和后端各自的职责是什么?
  • 转换过程中要保留原始语义(如何分析代码的语义?
  • 转换之后的代码在时间、空间和能耗上综合性能更高
  • 我们尤其关注运行时间(当前代码里面哪些代码对最终结果是无意义的,可以删除的?怎么通过一种新的逻辑能实现当前语义但计算量更小?

Proebsting's Law:编译器每隔18年可以把代码的算力提升一倍。

芯片硬件可以每年把算力提升60%,但编译器实际上只能每年提升4%,也就是叠加18年才能把算力提升一倍。尽管如此,由于编译器的优化的低成本,即使是4%,对大规模集群而言,也是非常可观的,因为软件优化相对于新增硬件采购的成本而言,基本上可以忽略不计。

1.4.2. 理解程序的底层语义

  • 代码的静态分析技术
  • 通过静态分析来优化性能
  • 通过静态分析来证明程序的正确性
  • 通过静态分析来发现代码的bug

代码性能优化的技术有很多:

  • 删除冗余拷贝
  • 常量折叠
  • Lazy Code Motion(延迟执行)
  • 寄存器分配
  • 循环展开
  • 值标记
  • 计算强度降维
  • 等等

哪些bug是编译器能发现的:

  • 空指针求值
  • 数组越界
  • 非法类转换
  • 缓冲区溢出漏洞
  • 整数溢出
  • 信息泄漏

下面代码里面有个安全漏洞,看看大家能否找出来?

 1 void read_matrix(int* data, char w, char h) {
 2     char buf_size = w * h;
 3     if (buf_size < BUF_SIZE) {
 4         int c0, c1;
 5         int buf[BUF_SIZE];
 6         for (c0 = 0; c0 < h; c0++) {
 7             for (c1 = 0; c1 < w; c1++) {
 8                 int index = c0 * w + c1;
 9                 buf[index] = data[index];
10             }
11         }
12         process(buf);
13     }
14 }

 

 

1.5. 课程主要内容

本课程的主要内容都可以在http://www.dcc.ufmg.br/~fernando/classes/dcc888 网站下载到,包括培训胶片,实验项目,课堂作业,参考链接。另外,链接里面还有整个课程的更新记录和之前对课程积分体系的讨论。

课程大纲:

1.导论
2.控制流图CFG
3.数据流分析
4.支撑数据流分析的算法
5.栅格
6.延迟运行
7.基于类型的分析
8.指针分析
9.循环优化
10.静态单赋值SSA
11.稀疏分析
12.污染流分析
13. 取值范围分析
14. 程序切片
15. 寄存器分配
16. 基于SSA的寄存器分配
17. 操作语义
18. 类型系统
19. 机械证明
20. 类型推导
21. JIT编译
22. 修正
23. 分支分析
24. 自动并行

当前最新的课程分为3部分27章,基于实用的原则,也考虑到时间成本,我们做了一些精简,主要讲其中的10章,上面被划掉的章节本次不讲,大家可以自己学习。

1.6. 常见的编译器的架构

每个编译器都有自己的前端、中端和后端,其中前端对接各种编程语言,对编程语言本身进行语法和语义分析,中端负责代码优化,后端对接各种硬件架构,负责生成对应硬件下的可执行软件。当前课程主要专注于中端的功能,也就是代码本身的分析和优化。

 

 

 

下面是一些常见的编译器框架,大家哪些框架用的比较多?

 

 

1.7. 完美编译器

完美的编译器,基于当前的程序P,生成Popt,后者保持同样的输入和输出流,但程序规模最少。

但由于输出很难界定,所以不可能存在完美的编译器。例如一个永不终止的程序的最简版本是这样的:

PleastL: goto L;
但这个代码不解决实际问题。

所以,对于任意一个图灵完备语言,总是有办法产生一个更好的编译器。

1.8. 为什么要学习编译器

1.8.1. 成为更好的程序员

gcc编译中有很多优化选项,下图是gcc5.4的不完全的优化选项列表:

 

 

如果想要知道这些优化选项分别是什么含义,并且应该在什么场合使用哪些特定的优化选项,什么情况下不能打开某个优化选项?这对程序的性能优化会非常有帮助。

通过对编译器的了解,也可以消除一些误解。

有的人认为少用变量名,所有变量都复用一个名称会减少内存占用?

也有人认为应该少用继承,这样函数调用过程中的遍历会减少?

使用宏比函数能减少函数调用过程中的开销?

1.8.2. 更多的工作机会

很多高级岗位都要求C/C++专家,熟悉计算机的理论。

很多大型公司本身就要发布自己的编译器版本。

还有一些专门做编译器或者编译优化的公司。

1.8.3. 更好的计算机科学家

理解编译器技术在geek圈也是非常酷的事。

1.9. 能从这次课程中获得的信息

编译器是计算机科学各种理论的综合系统。

1.9.1. 编译器涉及的理论知识

  • 算法(图论,集合论,动态规划)
  • 人工智能(贪婪算法,机器学习)
  • 自动机理论(DFA确定有限自动机,解析器生成器,上下文无关语法)
  • 线性代数(栅格,定点理论,伽罗瓦连接,类型系统)
  • 体系架构(流水线管理,内存体系架构,指令集)
  • 优化(运算研究,负荷均衡,打包,调度)

1.9.2. 动态分析

  • 打点采样,例如gprof
  • 测试用例生成,例如Klee
  • 仿真,例如valgrind,CFGGrind
  • 编排,例如AddressSanitizer

1.9.3. 静态分析

  • 数据流分析
  • 基于属性的分析
  • 类型分析

1.10. 理论基础

1.10.1. 图论

很多地方用到图论:

  • 控制流图
  • 属性图
  • 依赖图
  • 强连接组件图
  • 图的着色

1.10.2. 不动点原理

如果总的信息是有限的,每次迭代都会有新的信息增加的情况下,稳定的算法的迭代是否会终止?

1.10.3. 结构归纳法

 

1.10.4. 程序演示方法

  • 抽象语法树
  • 控制流图(SSA表)
  • 程序依赖图
  • 属性系统

1.11. 开源社区

  • gcc
  • LLVM
  • Mozilla's Monkey
  • Ocelot
  • Glasgow Haskell 编译器

1.12. 会议和杂志

  • PLDI: Programming Languages Design and Implementation
  • POPL: Principles of Programming Languages
  • ASPLOS: Architectural Support for Programming Languages and Operating Systems
  • CGO: Code Generation and Optimization
  • CC: Compiler Construction
  • TOPLAS – ACM Transactions on Programming Languages and Systems

1.13. 振奋人心的时代

  • Rust编程语言的所有者类型
  • Scala的路径依赖类型
  • Idris的依赖类型
  • Elixir角色模型中的大规模并行
  • 量子计算
  • 张量编译器
  • WebAssembly

1.14. 优化编译器简史

  • 1951-1952年,工作在Eckert-Mauchly Computer Corporation的Grace Hopper开发的面向UNIVAC I的A0系统是第一个有文献记载的编译器实现。
  • 第一代优化编译器,Fortran
  • 早期代码优化,Frances E. Allen和John Cocke引入控制流图,数据流分析,进程间数据流分析,工作列表算法
  • Gary Kildall,代码优化和分析之父,数据流单调框架,信息折叠,迭代算法,不动点原理
  • Abstract Interpretation,程序的静态行为表达
  • 寄存器分配,1981年Gregory Chaitin利用图着色理论引入了寄存器分配算法;1999年Poletto and Sarkar将线性扫描算法引入JIT编译器
  • SSA,80年代后期,Cytron et al. 引入SSA表格,这之后SSA经过多次改进,现在被广泛用到几乎所有编译器中
  • 1988年,Olin Shivers在PLDI上引入了控制流分析法,多人创立了指针分析法
  • 类型理论

1.15. 编译器的未来

  • 并行计算
  • 动态语言
  • 正确性
  • 安全

有关程序分析与优化 - 1 导论的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  6. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  9. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐