d的ldc的pgo优化

原文

三个月前,我使用LDC按配置文件优化(PGO)测量了D编译器前端代码,有7%的性能提升.现在,部分PGO工作于20161月20日合并到LDC主分支中!

LDC使用PGO,类似Clang使用PGO这里,大部分内容也适合Clang.

按配置优化(PGO)

PGO构建

配置文件可用不同的方式获得,但(目前)LDC仅支持指令分析.编译器在第一个编译阶段中添加"指令"代码.然后,使用要优化代码的用例输入,运行程序,并在退出时,输出配置文件数据到单独文件中.编译器在第二个编译阶段使用此配置文件数据文件来编译和优化.

1,使用指令编译:ldc2 -fprofile-instr-generate=profile.raw yourprogram.d -of=instrumented_program
2,用常见工作负载(要优化的工作负载类型)运行instrumented_program.这应该创建profile.raw文件.
3,转换原始配置文件为LDC可用格式:ldc-profdata merge -output=profile.data profile.raw

4,使用配置文件数据重新编译程序:ldc2 -O3 -fprofile-instr-use=profile.data yourprogram.d -of=optimized_program

指令会使程序明显变慢,但不必经常生成新的配置文件.在重新编译有更改程序时,可重用配置文件数据;LDC检测控制流的变化,并简单忽略过时的配置文件数据.

配置文件输出文件名

可在编译时指定,配置文件输出文件名,如上所示(-fprofile-instr-generate=).如果未指定文件名,则改用LLVM_PROFILE_FILE环境变量.如果未定义LLVM_PROFILE_FILE,则使用默认default.profraw文件名.

一般,单次运行程序不能创建有统计代表的配置文件.想使用不同的输入来分析多趟指令程序.每次运行程序,可设置LLVM_PROFILE_FILE为不同文件名,但配置文件运行时还支持配置文件名中的替换说明符来帮助你.分别用进程pid主机名替换文件名中的%p%h说明符.(%h需要LLVM>=3.9).

如果使用-fprofile-instr-generate=test.%p.raw编译并运行几次程序,最终会得到一组test.*.raw配置文件数据文件.然后,可如常使用ldc-profdata合并它们:

profdata merge -o test.profdata test.*.raw

检查配置文件数据

ldc-profdata工具(与构建ldc2时使用的LLVM版本匹配的llvm-profdata的重命名版本)可来检查配置文件数据文件的内容.看看如下:

extern(C)
void foo(int x) {
    if (x > 0) {
        if (x > 500) {
            // ...
        }
    }
}

void main() {
    foreach (i; 0 .. 1000) {
        foo(i);
    }
}

使用

ldc2 -fprofile-instr-generate=default.profraw -run test_foo.d

编译并运行它.然后,

ldc-profdata show -all-functions default.profraw

输出:

Counters:
  foo:
    Hash: 0x00000000000002cb
    Counters: 3
    Function count: 1000
  _Dmain:
    Hash: 0x0000000000000004
    Counters: 2
    Function count: 1
Functions shown: 2
Total functions: 2
Maximum function count: 1000
Maximum internal block count: 1000

配置文件数据文件包含有每个函数计数器和其他信息的数据结构.在此,看到程序只有两个指令函数:foo_Dmain.

foo函数包含3个执行计数器(每个if语句,函数项加上计数器),该函数被调用1000次(“函数计数”).在-fprofile-instr-use编译步骤中,LDC使用"hash"字段来检查是否可使用配置文件数据.

重用配置文件数据

LDC重用旧版本程序获得的配置文件数据.在源码稍有更改时允许,重用配置文件,在配置文件中存储函数控制流的哈希.在-fprofile-instr-use编译步骤中,LDC确保配置文件中的函数哈希等于正在编译代码的哈希.

如果没有匹配项,LDC忽略该函数的配置文件数据.哈希并不完美:不同代码段可能产生相同的哈希,因此对新代码可应用旧版本代码获得的配置文件.如:

void foo(int x) {
    if (x > 0) { // 1000x 真, 4x 假
        // ...
    }
}

foo函数几乎总是用正x调用,if语句的配置文件数据是"是分支有1000次,否分支有4次".如果代码现在从x>0更改为x<0,则函数哈希不变(当前LDC行为),且错误地使用了旧的配置文件数据.

void foo(int x) {
    if (x < 0) { // 用旧数据:1000x真, 4x假.错了
        // ...
    }
}

哈希可依赖更多代码来区分>和<式.目前,哈希主要是为了避免在配置文件信息不足或过多时出现编译错误,

如,只有if语句的函数才放入哈希中.哈希不完善是设计使然,因为某些更改不太可能更改旧配置文件数据的有效性.考虑将代码从x>1000更改为x>1001.此时,旧的配置文件数据可能仍足够好,丢弃它,性能会更糟.

无需苦恼:PGO不会改变D的语义,它只是个优化技术.因此,如果配置文件数据不再与修改后的代码匹配,则可能的最坏情况是程序性能下降.因为你已在(正确)测量有和没有PGO的程序性能,所以可轻松知道配置文件何时需要更新.

启动代码后重置配置文件

ldc.profile模块包含对接配置文件指令的函数.唯一真正有用的函数是resetAll().其他ldc.profile函数公开的函数是非常低级的,且在编译器/LLVM未来版本中可能不可用.resetAll()重置所有性能分析信息,并可从配置文件中删除程序启动行为:

import ldc.profile : resetAll;
void main()
{
    initializeProgram();
    resetAll();
    operate();
}

这样,PGO优化器,为了获得最佳操作()性能,会降低initializeProgram()的性能.

允许/禁止函数级分析

函数级分析实现,因此用新的(LDC_profile_instr,{true|false})指示打开/关闭指定函数的指令非常简单.指示指示(inline,...)有相同的语义.

void instrumented() {}
void not_instrumented() {
    pragma(LDC_profile_instr, false);
}
pragma(LDC_profile_instr, false) {
    void not_instrumented_2() {}

    void instrumented2_override() {
        pragma(LDC_profile_instr, true);
    }

    pragma(LDC_profile_instr, true):
    void instrumented3() {  }
    void instrumented4() {  }
}

pragma(LDC_profile_instr, false)
struct Strukt {
    void not_instrumented() {}
    void instrumented_method() {
        pragma(LDC_profile_instr, true);
    }
}

指令默认打开状态.因此,对非常可选指令,需要在每个文件的开头放pragma(LDC_profile_instr,false):.
但即便如此,也会指令导入函数.如果有足够的需求,则很容易实现默认关闭指令的命令行开关,因此仅指令一组指定函数就不会那么麻烦了.

你可能感兴趣的:(dlang,d,d,ldc,优化,pgo)