使用“time”选项探索编译器

2018年4月19日 · 作者:Björn Gustavsson

这是关于编译器的一系列博客文章的第一篇。 将会有关于编译器现在如何工作、未来可能如何工作以及一些历史记录来解释为什么有些事情是现在的样子的博客文章。 在这篇博客文章中,我将讨论用于探索编译器的最有用的选项之一,即 time 选项。

首先,让我们在一个具有许多函数和许多变量的巨大文件上看看 time 的实际效果,以便这些数字变得有趣

$ erlc +time NBAP-PDU-Contents.erl
Compiling "NBAP-PDU-Contents"
 remove_file                   :      0.000 s       6.5 kB
 parse_module                  :      0.709 s   25146.1 kB
 transform_module              :      0.000 s   25146.1 kB
 lint_module                   :      0.426 s   25146.1 kB
 expand_records                :      0.086 s   25993.7 kB
 core                          :      0.675 s  282518.3 kB
 sys_core_fold                 :      1.566 s  237885.4 kB
 core_transforms               :      0.000 s  237885.4 kB
 sys_core_bsm                  :      0.205 s  238982.3 kB
 sys_core_dsetel               :      0.108 s  238982.3 kB
 v3_kernel                     :      0.950 s  305320.5 kB
 v3_life                       :      0.453 s  221354.8 kB
 v3_codegen                    :      0.896 s   75801.0 kB
 beam_a                        :      0.080 s   75561.2 kB
 beam_reorder                  :      0.049 s   75561.2 kB
 beam_block                    :      0.361 s   87171.9 kB
 beam_except                   :      0.041 s   81557.7 kB
 beam_bs                       :      0.097 s   79929.2 kB
 beam_type                     :      0.502 s   77270.5 kB
 beam_split                    :      0.042 s   75004.5 kB
 beam_dead                     :      0.356 s   77566.7 kB
 beam_jump                     :      0.232 s   73347.9 kB
 beam_peep                     :      0.164 s   73346.0 kB
 beam_clean                    :      0.150 s   73081.0 kB
 beam_bsm                      :      0.092 s   75473.2 kB
 beam_receive                  :      0.020 s   75473.2 kB
 beam_record                   :      0.023 s   75471.4 kB
 beam_trim                     :      0.042 s   75471.4 kB
 beam_flatten                  :      0.071 s   66745.5 kB
 beam_z                        :      0.019 s   66442.2 kB
 beam_validator                :      0.401 s   66442.2 kB
 beam_asm                      :      0.236 s       6.5 kB
 save_binary                   :      0.000 s       6.5 kB

当给出 time 选项时,编译器将在执行每个编译器传递后打印一行。 每行的第一个是编译器传递的名称。 通常(但并非总是)该名称是实现编译器传递的 Erlang 模块的名称。

名称后面是编译器运行该编译器传递所花费的时间(以秒为单位)。 对于较小的文件,时间通常为零或接近于零。 对于这个巨大的文件,大多数时间都是非零的。 例如,sys_core_fold 传递需要大约一秒半的时间来完成其工作。

时间后面是该编译器传递使用的内存量。

在这篇博客文章中,我将只讨论一些编译器传递。 在以后的博客文章中,将会有更多关于编译器传递的作用的内容。

remove_file 传递是运行的第一个传递。 它会删除任何现有的 BEAM 文件,以防编译失败,不会存在过时的 BEAM 文件。 最后一个传递是 save_binary 传递。 它将包含 BEAM 代码的二进制文件保存到 BEAM 文件。

现在让我们看看如果我们给出 -S 选项,输出会如何变化

$ erlc -S +time NBAP-PDU-Contents.erl
Compiling "NBAP-PDU-Contents"
 parse_module                  :      0.718 s   25146.1 kB
 transform_module              :      0.000 s   25146.1 kB
 lint_module                   :      0.420 s   25146.1 kB
 expand_records                :      0.088 s   25993.8 kB
 core                          :      0.671 s  282518.3 kB
 sys_core_fold                 :      1.564 s  237885.4 kB
 core_transforms               :      0.000 s  237885.4 kB
 sys_core_bsm                  :      0.203 s  238982.3 kB
 sys_core_dsetel               :      0.104 s  238982.3 kB
 v3_kernel                     :      0.964 s  305320.5 kB
 v3_life                       :      0.375 s  221354.8 kB
 v3_codegen                    :      1.044 s   75801.0 kB
 beam_a                        :      0.091 s   75561.3 kB
 beam_reorder                  :      0.044 s   75561.3 kB
 beam_block                    :      0.276 s   87171.9 kB
 beam_except                   :      0.028 s   81557.8 kB
 beam_bs                       :      0.103 s   79929.3 kB
 beam_type                     :      0.518 s   77270.5 kB
 beam_split                    :      0.049 s   75004.6 kB
 beam_dead                     :      0.379 s   77566.8 kB
 beam_jump                     :      0.195 s   73347.9 kB
 beam_peep                     :      0.156 s   73346.0 kB
 beam_clean                    :      0.168 s   73081.0 kB
 beam_bsm                      :      0.070 s   75473.2 kB
 beam_receive                  :      0.044 s   75473.2 kB
 beam_record                   :      0.021 s   75471.5 kB
 beam_trim                     :      0.041 s   75471.5 kB
 beam_flatten                  :      0.045 s   66745.5 kB
 beam_z                        :      0.016 s   66442.2 kB
 listing                       :      1.503 s   66442.2 kB

我们可以看到传递列表是如何变化的。 现在最后运行的传递是 listing,它会在 .S 文件中生成 BEAM 汇编代码的列表。 开头的 remove_file 传递不会运行,因为不会生成 BEAM 文件,并且应该保留任何现有的 BEAM 文件。

让我们尝试其中一个未公开的调试选项

$ erlc +no_postopt +time NBAP-PDU-Contents.erl
Compiling "NBAP-PDU-Contents"
 remove_file                   :      0.000 s       6.5 kB
 parse_module                  :      0.706 s   25146.1 kB
 transform_module              :      0.000 s   25146.1 kB
 lint_module                   :      0.421 s   25146.1 kB
 expand_records                :      0.090 s   25993.8 kB
 core                          :      0.684 s  282518.3 kB
 sys_core_fold                 :      1.614 s  237885.4 kB
 core_transforms               :      0.000 s  237885.4 kB
 sys_core_bsm                  :      0.210 s  238982.3 kB
 sys_core_dsetel               :      0.105 s  238982.3 kB
 v3_kernel                     :      0.967 s  305320.5 kB
 v3_life                       :      0.353 s  221354.8 kB
 v3_codegen                    :      1.028 s   75801.0 kB
 beam_a                        :      0.091 s   75561.3 kB
 beam_clean                    :      0.201 s   73513.2 kB
 beam_z                        :      0.023 s   72897.9 kB
 beam_validator                :      0.467 s   72897.9 kB
 beam_asm                      :      0.396 s       6.6 kB
 save_binary                   :      0.001 s       6.5 kB

我们可以看到运行的传递要少得多。 no_postopt 选项会关闭在 BEAM 代码上运行的所有优化(即 v3_codegen 之后的所有优化)。

那么为什么这个 time 选项有用呢? #

  • 当模块的编译非常缓慢时,time 可以显示是否有任何特定的传递是瓶颈(比其他传递慢得多)。 事实上,很久以前,编译器需要几分钟才能编译我在本博客文章中用作示例的 NBAP-PDU-Contents 模块。 time 选项立即指出了我需要修复的瓶颈。

  • 如果编译器在编译某个模块时没有终止,time 将显示上次成功运行的传递(导致问题的传递之前的一个)。

  • 编译器会忽略它无法识别的选项,因此如果您记错或拼错了一个选项,编译器将不会按照您的预期执行。 添加 time 选项可以帮助您验证是否运行了预期的编译器传递。

所有这些未公开的选项都在哪里记录? #

有许多用于调试的选项,允许您跳过某些优化传递或在某个传递后生成代码列表。

可以通过从 Erlang shell 运行 compile:options/0 来显示大多数这些选项

$ erl
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V9.2  (abort with ^G)
1> compile:options().
dpp - Generate .pp file
'P' - Generate .P source listing file
dabstr - Generate .abstr file
debug_info - Run save_abstract_code
dexp - Generate .expand file
'E' - Generate .E source listing file
dcore - Generate .core file
clint0 - Run core_lint_module
doldinline - Generate .oldinline file
dcorefold - Generate .corefold file
dinline - Generate .inline file
dcopt - Generate .copt file
.
.
.

需要思考的点 #

为什么一些编译器传递的名称以 v3 开头? 关注此博客,未来的博客文章中可能会有答案。