原文
使用LDC
的(LTO)
链接时优化
的简短文章,包含演示了如何提高程序性能
的简单
示例.因为LTO
在LLVMIR
级别工作,因此可跨越C++/D
语言优化!
重要提示:LDC/LLVM
的LTO
在窗口
上不可用.
(LTO)
链接时优化是指链接时
的程序优化.链接器提取
所有目标文件在一起,并合并
到一个程序
中.链接器
可看见整个程序,因此可分析和优化整个
程序.
但是,一般链接器只有在程序已转换为机器码
时才可见程序.在该级别上,应仍可优化
,但这很难.不能使用GCC
或LLVM
的优化器.
与GCC
相同,LLVM
的LTO
机制基于传递LLVM
优化器可理解的(LLVMIR)
代码给链接器
,从而可在链接过程中执行全程序分析和优化
.
所谓的"完整"LTO
组合单独
目标文件的所有LLVMIR
代码到一个大的LLVM
模块中,然后优化它并如常生成
机器码.
"ThinLTO"
分开模块,但从其他模块
按需导入函数,并并行
优化和生成机器码
.更多
可在一次编译
调用中改进全LTO
的所有优化,一次编译
就可.一次编译所有是配音(dub)
,也是目前LDC
自身(D
部分)的构建方式.
LTO
而不是一次
编译的优点是,LTO
的(部分
)编译是并行
完成的.对完整的LTO(-flto=full)
,只有语义
分析是并行完成的,而优化
和生成机器码
是在单线程中完成的.
对ThinLTO(-flto=thin)
,除全局
分析步骤外,所有步骤
都是并行
完成的.因此,ThinLTO
比全LTO
或一次
编译快得多,尤其是在拥有多个可用内核
机器时.
要使用LTO
,只需要在命令行
上指定-flto=thin
或-flto=full
!
LTO
的工作方式是,编译器输出的目标
文件不是普通
目标文件:它们是LLVMIR
位码文件,仅由目标文件扩展名
伪装成目标文件.表明
链接器必须支持此LLVM的LTO
机制.
在MacOSX
上,LLVM/Clang
用作系统
编译器,链接器知道如何用libLTO.DYLIB
库处理LTO
.适合MacOSX
的LDC
包附带
此库,因此它是LDC
的LLVM
版本的最新版本.
在Linux
上,黄金
链接器支持
插件,LLVM
黄金插件用来处理LTO
.
然后,可复制二进制文件
到LDC
的lib
目录,或传递-flto-binary=
给LDC
,以便链接器可找到它.
LTO
选项(如增量
构建的ThinLTO
缓存)可像一般的链接器选项
一样传递:
OS X: ldc2 -L-cache_path_lto -L/path/to/cache ...
gold: ldc2 -L-plugin-opt=cache-dir=/path/to/cache ...
考虑以下示例,代码
分布在(lto_a.d
和lto_b.d
)两个文件中:
// File lto_b.d
//用`extern(C++)`来允许用`C++`定义它
extern(C++) void doesNothing() {}
// File lto_a.d
extern(C++) void doesNothing(); //仅声明
void main() {
for (ulong i = 0; i < 1_000_000_000; ++i) {
doesNothing();
}
}
先编译lto_b.d
为lto_b.o
,然后再编译lto_a.d
,并与lto_b.o
链接.该程序闲着
,优化器应可弄清楚这一点,但是,优化器不能.编译lto_a.d
时,它不知道Nothing()
做了什么,因此不能做太多优化
:程序循环1亿次
调用立即返回
的函数.在我机器上,大约需要2秒:
> ldc2 -c -O3 lto_b.d -of=lto_b.o
> ldc2 -O3 lto_a.d lto_b.o -of=program
> time ./program
./program 1.81s user 0.01s system 98% cpu 1.845 total
使用LTO
,导入doesNothing()
到lto_a
模块中,优化器
可发挥其神力:
> ldc2 -c -O3 -flto=thin lto_b.d -of=lto_b.o
> ldc2 -O3 -flto=thin lto_a.d lto_b.o -of=program_lto
> time ./program_lto
./program_lto 0.00s 用户 0.00s 系统 28% CPU 总计 0.012 总计
一次
编译器调用中,编译所有源码
可获得相同
运行时:
> ldc2 -O3 lto_a.d lto_b.d -of=program_allatonce
> time ./program_allatonce
./program_allatonce 0.00s user 0.00s system 44% cpu 0.008 total
C++/D
语言障碍D可(相对)轻松地与C++
代码互操作.LDC
自身就是个很好
示例:LDC
的前端是用D
编写的,而它的后端(LLVM)
是用C++
编写的.
但是,不能一次
编译所有源码
来跨C++/D
语言优化
,因为C++
和D
的编译器都无法理解对方.因此,如下C++
函数不会内联到D函数
中:
// File lto_b.cpp
void doesNothing() {}
> clang -c -O3 lto_b.cpp -o lto_b.o
> ldc2 -O3 lto_a.d lto_b.o -of=program_cpp
> time ./program_cpp
./program_cpp 2.09s user 0.01s system 99% cpu 2.125 total
好消息是:LTO
没有语言障碍.因为LTO
在LLVMIR
级别工作,且LDC
和Clang
都编译为相同的LLVMIR
语言,因此仅C++
,仅D
和C++/D
混合程序可实现相同优化
潜力!
对给定示例
,可通过以下构建步骤
减少执行时间
到"零"
:
> clang -c -O3 -flto=thin lto_b.cpp -o lto_b.o
> ldc2 -O3 -flto=thin lto_a.d lto_b.o -of=program_cpp_lto -mtriple=x86_64-apple-macosx10.11.0
> time ./program_cpp_lto
./program_cpp_lto 0.00s user 0.00s system 61% cpu 0.005 total
注意,调用ldc2
时,必须显式
指定目标
三元组(这仅在OSX
上需要).在MacOSX
上,LDC
和Clang
默认使用
略有不同的三元组
.
当三元组
不同时,LTO
代码生成器会抱怨.有趣的是,反向
调用编译器
时不需要显式
提及三元组
,但是必须显式传递D运行时
库给Clang
.